]> granicus.if.org Git - icu/commitdiff
ICU-10268 Take Measurement API out of technology preview and have it match the curren...
authorTravis Keep <keep94@gmail.com>
Fri, 13 Dec 2013 18:26:18 +0000 (18:26 +0000)
committerTravis Keep <keep94@gmail.com>
Fri, 13 Dec 2013 18:26:18 +0000 (18:26 +0000)
X-SVN-Rev: 34765

12 files changed:
icu4j/main/classes/core/src/com/ibm/icu/text/CurrencyFormat.java
icu4j/main/classes/core/src/com/ibm/icu/text/GeneralMeasureFormat.java [deleted file]
icu4j/main/classes/core/src/com/ibm/icu/text/MeasureFormat.java
icu4j/main/classes/core/src/com/ibm/icu/text/TimeUnitFormat.java
icu4j/main/classes/core/src/com/ibm/icu/util/Currency.java
icu4j/main/classes/core/src/com/ibm/icu/util/FormatWidth.java [deleted file]
icu4j/main/classes/core/src/com/ibm/icu/util/Measure.java
icu4j/main/classes/core/src/com/ibm/icu/util/MeasureUnit.java
icu4j/main/classes/core/src/com/ibm/icu/util/TimeUnit.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/MeasureUnitTest.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/serializable/CompatibilityTest.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/serializable/SerializableTest.java

index 8a287706c9e916ffaae9bc5e4eddbe97362f30c1..de5be619c51ba85f3d736833aa15b53e2ee7767b 100644 (file)
 */
 package com.ibm.icu.text;
 
-import java.io.Serializable;
+import java.io.ObjectStreamException;
 import java.text.FieldPosition;
 import java.text.ParsePosition;
 
 import com.ibm.icu.util.CurrencyAmount;
+import com.ibm.icu.util.Measure;
 import com.ibm.icu.util.ULocale;
 
 /**
@@ -29,13 +30,15 @@ import com.ibm.icu.util.ULocale;
  * @see com.ibm.icu.text.DecimalFormat
  * @author Alan Liu
  */
-class CurrencyFormat extends MeasureFormat implements Serializable {
+class CurrencyFormat extends MeasureFormat {
     // Generated by serialver from JDK 1.4.1_01
     static final long serialVersionUID = -931679363692504634L;
     
     private NumberFormat fmt;
+    private transient final MeasureFormat mf;
 
     public CurrencyFormat(ULocale locale) {
+        mf = MeasureFormat.getInstance(locale, FormatWidth.WIDE);
         fmt = NumberFormat.getCurrencyInstance(locale.toLocale());
     }
 
@@ -44,12 +47,15 @@ class CurrencyFormat extends MeasureFormat implements Serializable {
      * @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition)
      */
     public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
-        try {
-            CurrencyAmount currency = (CurrencyAmount) obj;
+        if (!(obj instanceof CurrencyAmount)) {
+            throw new IllegalArgumentException("Invalid type: " + obj.getClass().getName());
+        }
+        CurrencyAmount currency = (CurrencyAmount) obj;
+            
+        // Since we extend MeasureFormat, we have to maintain thread-safety.
+        synchronized (fmt) {
             fmt.setCurrency(currency.getCurrency());
             return fmt.format(currency.getNumber(), toAppendTo, pos);
-        } catch (ClassCastException e) {
-            throw new IllegalArgumentException("Invalid type: " + obj.getClass().getName());
         }
     }
 
@@ -57,7 +63,69 @@ class CurrencyFormat extends MeasureFormat implements Serializable {
      * Override Format.parseObject().
      * @see java.text.Format#parseObject(java.lang.String, java.text.ParsePosition)
      */
-    public Object parseObject(String source, ParsePosition pos) {
-        return fmt.parseCurrency(source, pos);
+    @Override
+    public CurrencyAmount parseObject(String source, ParsePosition pos) {
+        synchronized (fmt) {
+            return fmt.parseCurrency(source, pos);
+        }
+    }
+    
+    // boilerplate code to make CurrencyFormat otherwise follow the contract of
+    // MeasureFormat
+    
+    @Override
+    public String formatMeasures(Measure... measures) {
+        return mf.formatMeasures(measures);
+    }
+    
+    @Override
+    public <T extends Appendable> T formatMeasure(
+            Measure measure, T appendable, FieldPosition fieldPosition) {
+        return mf.formatMeasure(measure, appendable, fieldPosition);
+    }
+    
+    @Override
+    public <T extends Appendable> T formatMeasures(
+            T appendable, FieldPosition fieldPosition, Measure... measures) {
+        return mf.formatMeasures(appendable, fieldPosition, measures);
+    }
+    
+    @Override
+    public MeasureFormat.FormatWidth getWidth() {
+        return mf.getWidth();
+    }
+    
+    @Override
+    public ULocale getLocale() {
+        return mf.getLocale();
+    }
+    
+    @Override
+    public NumberFormat getNumberFormat() {
+        return mf.getNumberFormat();
+    }
+    
+    // End boilerplate.
+    
+    
+    @Override
+    public int hashCode() {
+        return getLocale().hashCode() + 154321962;
+    }
+    
+    @Override
+    protected boolean equalsSameClass(MeasureFormat other) {
+        return getLocale().equals(other.getLocale());
+    }
+    
+    // Serialization
+    
+    private Object writeReplace() throws ObjectStreamException {
+        return mf.toCurrencyProxy();
+    }
+    
+    // Preserve backward serialize backward compatibility.
+    private Object readResolve() throws ObjectStreamException {
+        return new CurrencyFormat(fmt.getLocale(ULocale.ACTUAL_LOCALE));
     }
 }
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/GeneralMeasureFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/text/GeneralMeasureFormat.java
deleted file mode 100644 (file)
index 37a7f61..0000000
+++ /dev/null
@@ -1,580 +0,0 @@
-/*
- *******************************************************************************
- * 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.ULocale.Category;
-import com.ibm.icu.util.UResourceBundle;
-
-/**
- * Mutable class for formatting GeneralMeasures, or sequences of them.
- * @author markdavis
- * @internal
- * @deprecated This API is ICU internal only.
- */
-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;
-
-    /**
-     * @internal
-     * @deprecated This API is ICU internal only.
-     */
-    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
-     * @internal
-     * @deprecated This API is ICU internal only.
-     */
-    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
-     * @internal
-     * @deprecated This API is ICU internal only.
-     */
-    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
-     * @internal
-     * @deprecated This API is ICU internal only.
-     */
-    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
-     * @internal
-     * @deprecated This API is ICU internal only.
-     */
-    public static MeasureFormat getCurrencyFormat() {
-        return getCurrencyFormat(ULocale.getDefault(Category.FORMAT));
-    }
-
-    /**
-     * @return the locale of the format.
-     * @internal
-     * @deprecated This API is ICU internal only.
-     */
-    public ULocale getLocale() {
-        return locale;
-    }
-
-    /**
-     * @return the desired length for the format
-     * @internal
-     * @deprecated This API is ICU internal only.
-     */
-    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;
-    }
-
-    /**
-     * @internal
-     * @deprecated This API is ICU internal only.
-     */
-    @SuppressWarnings("unchecked")
-    @Override
-    public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
-        if (obj instanceof Collection) {
-            Collection<Measure> coll = (Collection<Measure>) obj;
-            return format(toAppendTo, pos, coll.toArray(new Measure[coll.size()]));
-        } else if (obj instanceof Measure[]) {
-            return format(toAppendTo, pos, (Measure[]) obj);
-        } else {
-            return format((Measure) obj, toAppendTo, pos);
-        }
-    }
-
-    /**
-     * Format a general measure (type-safe).
-     * @param measure the measure to format
-     * @param toAppendTo as in {@link #format(Object, StringBuffer, FieldPosition)}
-     * @param pos as in {@link #format(Object, StringBuffer, FieldPosition)}
-     * @return passed-in buffer with appended text.
-     * @internal
-     * @deprecated This API is ICU internal only.
-     */
-    public StringBuffer format(Measure measure, StringBuffer toAppendTo, FieldPosition pos) {
-        Number n = measure.getNumber();
-        MeasureUnit unit = measure.getUnit();        
-        UFieldPosition fpos = new UFieldPosition(pos.getFieldAttribute(), pos.getField());
-        StringBuffer formattedNumber = numberFormat.format(n, new StringBuffer(), fpos);
-        String keyword = rules.select(new PluralRules.FixedDecimal(n.doubleValue(), fpos.getCountVisibleFractionDigits(), fpos.getFractionDigits()));
-
-        Map<FormatWidth, Map<String, PatternData>> styleToCountToFormat = unitToStyleToCountToFormat.get(unit);
-        Map<String, PatternData> countToFormat = styleToCountToFormat.get(length);
-        PatternData messagePatternData = countToFormat.get(keyword);
-
-        toAppendTo.append(messagePatternData.prefix);
-        if (messagePatternData.suffix != null) { // there is a number (may not happen with, say, Arabic dual)
-            // Fix field position
-            pos.setBeginIndex(fpos.getBeginIndex() + messagePatternData.prefix.length());
-            pos.setEndIndex(fpos.getEndIndex() + messagePatternData.prefix.length());
-            toAppendTo.append(formattedNumber);
-            toAppendTo.append(messagePatternData.suffix);
-        }
-        return toAppendTo;
-    }
-
-
-    /**
-     * Format a sequence of measures.
-     * @param toAppendto as in {@link #format(Object, StringBuffer, FieldPosition)}
-     * @param pos as in {@link #format(Object, StringBuffer, FieldPosition)}
-     * @param measures a sequence of one or more measures.
-     * @return passed-in buffer with appended text.
-     * @internal
-     * @deprecated This API is ICU internal only.
-     */
-    public StringBuffer format(StringBuffer toAppendto, FieldPosition pos, Measure... measures) {
-        StringBuffer[] results = new StringBuffer[measures.length];
-        for (int i = 0; i < measures.length; ++i) {
-            results[i] = format(measures[i], new StringBuffer(), pos);
-        }
-        ListFormatter listFormatter = ListFormatter.getInstance(locale, 
-                length == FormatWidth.WIDE ? ListFormatter.Style.DURATION : ListFormatter.Style.DURATION_SHORT);
-        return toAppendto.append(listFormatter.format((Object[]) results));
-    }
-
-    /**
-     * Format a sequence of measures.
-     * @param measures a sequence of one or more measures.
-     * @return passed-in buffer with appended text.
-     * @internal
-     * @deprecated This API is ICU internal only.
-     */
-    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 synchronized 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;
-        }
-    }
-    
-    /**
-     * @internal
-     * @deprecated This API is ICU internal only.
-     */
-    @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);
-        }
-    };
-
-    /**
-     * @internal
-     * @deprecated This API is ICU internal only.
-     */
-    @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);
-    }
-    
-    /**
-     * @internal
-     * @deprecated This API is ICU internal only.
-     */
-    @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 static final long serialVersionUID = -6033308329886716770L;
-
-        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);
-        }
-    }
-}
\ No newline at end of file
index 035265776ef2f19b4fda30e9c3fdf823967980c2..ccf6fa2823864c2356c718c22327c076793de13f 100644 (file)
@@ -1,6 +1,6 @@
 /*
 **********************************************************************
-* Copyright (c) 2004-2011, International Business Machines
+* Copyright (c) 2004-2013, International Business Machines
 * Corporation and others.  All Rights Reserved.
 **********************************************************************
 * Author: Alan Liu
 */
 package com.ibm.icu.text;
 
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.InvalidObjectException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.io.ObjectStreamException;
+import java.text.FieldPosition;
+import java.text.ParsePosition;
+import java.util.Collection;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.MissingResourceException;
+import java.util.Set;
+
+import com.ibm.icu.impl.ICUResourceBundle;
+import com.ibm.icu.impl.SimpleCache;
+import com.ibm.icu.util.Currency;
+import com.ibm.icu.util.Measure;
+import com.ibm.icu.util.MeasureUnit;
 import com.ibm.icu.util.ULocale;
 import com.ibm.icu.util.ULocale.Category;
+import com.ibm.icu.util.UResourceBundle;
 
 /**
- * A formatter for Measure objects.  This is an abstract base class.
+ * A formatter for Measure objects.
  *
- * <p>To format or parse a Measure object, first create a formatter
+ * <p>To format a Measure object, first create a formatter
  * object using a MeasureFormat factory method.  Then use that
- * object's format and parse methods.
+ * object's format, formatMeasure, or formatMeasures methods.
+ * 
+ * Here is sample code:
+ * <pre>
+ *      MeasureFormat fmtFr = MeasureFormat.getInstance(
+ *              ULocale.FRENCH, FormatWidth.SHORT);
+ *      Measure measure = new Measure(23, MeasureUnit.CELSIUS);
+ *      
+ *      // Output: 23 °C
+ *      System.out.println(fmtFr.format(measure));
+ *
+ *      Measure measureF = new Measure(70, MeasureUnit.FAHRENHEIT);
+ *
+ *      // Output: 70 °F
+ *      System.out.println(fmtFr.format(measureF));
+ *     
+ *      MeasureFormat fmtFrFull = MeasureFormat.getInstance(
+ *              ULocale.FRENCH, FormatWidth.WIDE);
+ *      // Output: 70 pieds, 5,3 pouces
+ *      System.out.println(fmtFrFull.formatMeasures(
+ *              new Measure(70, MeasureUnit.FOOT),
+ *              new Measure(5.3, MeasureUnit.INCH)));
+ *              
+ *      // Output: 1 pied, 1 pouce
+ *      System.out.println(fmtFrFull.formatMeasures(
+ *              new Measure(1, MeasureUnit.FOOT),
+ *              new Measure(1, MeasureUnit.INCH)));
+ *      }
+ *      
+ *      MeasureFormat fmtEn = MeasureFormat.getInstance(ULocale.ENGLISH, FormatWidth.WIDE);
+ *      
+ *      // Output: 1 inch, 2 feet
+ *      fmtEn.formatMeasures(
+ *              new Measure(1, MeasureUnit.INCH),
+ *              new Measure(2, MeasureUnit.FOOT));
+ * </pre>
+ * <p>
+ * This class does not do conversions from one unit to another. It simply formats
+ * whatever units it is given
+ * <p>
+ * This class is immutable so long as no mutable subclass, namely TimeUnitFormat,
+ * is used. Although this class has existing subclasses, this class does not
+ * support new sub-classes.   
  *
  * @see com.ibm.icu.text.UFormat
  * @author Alan Liu
  * @stable ICU 3.0
  */
-public abstract class MeasureFormat extends UFormat {
+public class MeasureFormat extends UFormat {
+    
+
     // Generated by serialver from JDK 1.4.1_01
     static final long serialVersionUID = -7182021401701778240L;
+    
+    private final transient ULocale locale;
+    
+    // NumberFormat is known to lack thread-safety, all access to this
+    // field must be synchronized.
+    private final transient NumberFormat numberFormat;
+    
+    private final transient FormatWidth length;
+    
+    // PluralRules is documented as being immutable which implies thread-safety.
+    private final transient PluralRules rules;
+    
+    // Measure unit -> format width -> plural form -> pattern ("{0} meters")
+    private final transient Map<MeasureUnit, EnumMap<FormatWidth, Map<String, PatternData>>> unitToStyleToCountToFormat;
+
+    static final SimpleCache<ULocale,Map<MeasureUnit, EnumMap<FormatWidth, Map<String, PatternData>>>> localeToUnitToStyleToCountToFormat
+            = new SimpleCache<ULocale,Map<MeasureUnit, EnumMap<FormatWidth, Map<String, PatternData>>>>();
+    
+    // For serialization: sub-class types.
+    private static final int MEASURE_FORMAT = 0;
+    private static final int TIME_UNIT_FORMAT = 1;
+    private static final int CURRENCY_FORMAT = 2;
+    
+    /**
+     * Formatting width enum.
+     * 
+     * @draft ICU 53
+     * @provisional
+     */
+    public static enum FormatWidth {
+        
+        /**
+         * Spell out everything.
+         * 
+         * @draft ICU 53
+         * @provisional
+         */
+        WIDE("units"), 
+        
+        /**
+         * Abbreviate when possible.
+         * 
+         * @draft ICU 53
+         * @provisional
+         */
+        SHORT("unitsShort"), 
+        
+        /**
+         * Brief. Use only a symbol for the unit when possible.
+         * 
+         * @draft ICU 53
+         * @provisional
+         */
+        NARROW("unitsNarrow");
+    
+        final String resourceKey;
+    
+        private FormatWidth(String resourceKey) {
+            this.resourceKey = resourceKey;
+        }
+    }
+    
+    /**
+     * Create a format from the locale, length, and format.
+     *
+     * @param locale the locale.
+     * @param width hints how long formatted strings should be.
+     * @return The new MeasureFormat object.
+     * @draft ICU 53
+     * @provisional
+     */
+    public static MeasureFormat getInstance(ULocale locale, FormatWidth width) {
+        return getInstance(locale, width, NumberFormat.getInstance(locale));
+    }
+    
+    /**
+     * Create a format from the locale, length, and format.
+     *
+     * @param locale the locale.
+     * @param width hints how long formatted strings should be.
+     * @param format This is defensively copied.
+     * @return The new MeasureFormat object.
+     * @draft ICU 53
+     * @provisional
+     */
+    public static MeasureFormat getInstance(ULocale locale, FormatWidth width, NumberFormat format) {
+        PluralRules rules = PluralRules.forLocale(locale);
+        Map<MeasureUnit, EnumMap<FormatWidth, Map<String, PatternData>>> unitToStyleToCountToFormat; 
+        unitToStyleToCountToFormat = localeToUnitToStyleToCountToFormat.get(locale);
+        if (unitToStyleToCountToFormat == null) {
+            unitToStyleToCountToFormat = loadLocaleData(locale, rules);
+            localeToUnitToStyleToCountToFormat.put(locale, unitToStyleToCountToFormat);
+        }
+        return new MeasureFormat(locale, width, format, rules, unitToStyleToCountToFormat);
+    }
+    
+    /**
+     * Format a sequence of measures. Uses the ListFormatter unit lists.
+     * So, for example, one could format “3 feet, 2 inches”.
+     * Zero values are formatted (eg, “3 feet, 0 inches”). It is the caller’s
+     * responsibility to have the appropriate values in appropriate order,
+     * and using the appropriate Number values. Typically the units should be
+     * in descending order, with all but the last Measure having integer values
+     * (eg, not “3.2 feet, 2 inches”).
+     * 
+     * @param measures a sequence of one or more measures.
+     * @return the formatted string.
+     * @draft ICU 53
+     * @provisional
+     */
+    public String formatMeasures(Measure... measures) {
+        StringBuilder result = this.formatMeasures(
+                new StringBuilder(), new FieldPosition(0), measures);
+        return result.toString();
+    }
+    
+    /**
+     * Able to format Collection&lt;? extends Measure&gt;, Measure[], and Measure
+     * by delegating to formatMeasure or formatMeasures.
+     * If the pos argument identifies a field used by the format
+     * then its indices are set to the beginning and end of the first such field
+     * encountered.
+     * 
+     * @param obj must be a Collection<? extends Measure>, Measure[], or Measure object.
+     * @param toAppendTo Formatted string appended here.
+     * @pram pos Identifies a field in the formatted text.
+     * @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition)
+     * 
+     * @draft ICU53
+     * @provisional
+     */
+    @Override
+    public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
+        if (obj instanceof Collection) {
+            Collection<?> coll = (Collection<?>) obj;
+            Measure[] measures = new Measure[coll.size()];
+            int idx = 0;
+            for (Object o : coll) {
+                if (!(o instanceof Measure)) {
+                    throw new IllegalArgumentException(obj.toString());
+                }
+                measures[idx++] = (Measure) o;
+            }
+            return formatMeasures(toAppendTo, pos, measures);
+        } else if (obj instanceof Measure[]) {
+            return formatMeasures(toAppendTo, pos, (Measure[]) obj);
+        } else if (obj instanceof Measure){
+            return this.formatMeasure((Measure) obj, toAppendTo, pos);
+        } else {
+            throw new IllegalArgumentException(obj.toString());            
+        }
+    }
+    
+    /**
+     * @see java.text.Format#parseObject(java.lang.String, java.text.ParsePosition)
+     * @throws UnsupportedOperationException Not supported.
+     * @draft ICU 53
+     * @provisional
+     */
+    @Override
+    public Measure parseObject(String source, ParsePosition pos) {
+        throw new UnsupportedOperationException();
+    }
+
+    
+    /**
+     * Formats a single measure and adds to appendable.
+     * If the fieldPosition argument identifies a field used by the format,
+     * then its indices are set to the beginning and end of the first such
+     * field encountered.
+     * 
+     * @param measure the measure to format
+     * @param appendable the formatted string appended here.
+     * @param fieldPosition Identifies a field in the formatted text.
+     * @return appendable.
+     * @see MeasureFormat#formatMeasures(Measure...)
+     * @draft ICU 53
+     * @provisional
+     */
+    public <T extends Appendable> T formatMeasure(
+            Measure measure, T appendable, FieldPosition fieldPosition) {
+        Number n = measure.getNumber();
+        MeasureUnit unit = measure.getUnit();        
+        UFieldPosition fpos = new UFieldPosition(fieldPosition.getFieldAttribute(), fieldPosition.getField());
+        StringBuffer formattedNumber = numberFormat.format(n, new StringBuffer(), fpos);
+        String keyword = rules.select(new PluralRules.FixedDecimal(n.doubleValue(), fpos.getCountVisibleFractionDigits(), fpos.getFractionDigits()));
 
+        Map<FormatWidth, Map<String, PatternData>> styleToCountToFormat = unitToStyleToCountToFormat.get(unit);
+        Map<String, PatternData> countToFormat = styleToCountToFormat.get(length);
+        PatternData messagePatternData = countToFormat.get(keyword);
+        try {
+            appendable.append(messagePatternData.prefix);
+            if (messagePatternData.suffix != null) { // there is a number (may not happen with, say, Arabic dual)
+                // Fix field position
+                if (fpos.getBeginIndex() != 0 || fpos.getEndIndex() != 0) {
+                    fieldPosition.setBeginIndex(fpos.getBeginIndex() + messagePatternData.prefix.length());
+                    fieldPosition.setEndIndex(fpos.getEndIndex() + messagePatternData.prefix.length());
+                }
+                appendable.append(formattedNumber);
+                appendable.append(messagePatternData.suffix);
+            }
+            return appendable;
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+    
+    /**
+     * Formats a sequence of measures and adds to appendable.
+     * If the fieldPosition argument identifies a field used by the format,
+     * then its indices are set to the beginning and end of the first such
+     * field encountered.
+     * 
+     * @param appendable the formatted string appended here.
+     * @param fieldPosition Identifies a field in the formatted text.
+     * @param measures the measures to format.
+     * @return appendable.
+     * @see MeasureFormat#formatMeasures(Measure...)
+     * @draft ICU 53
+     * @provisional
+     */
+    @SuppressWarnings("unchecked")
+    public <T extends Appendable> T formatMeasures(
+            T appendable, FieldPosition fieldPosition, Measure... measures) {
+        
+        // Zero out our field position so that we can tell when we find our field.
+        FieldPosition fpos = new FieldPosition(fieldPosition.getFieldAttribute(), fieldPosition.getField());
+        FieldPosition dummyPos = new FieldPosition(0);
+        
+        int fieldPositionFoundIndex = -1;
+        StringBuilder[] results = new StringBuilder[measures.length];
+        for (int i = 0; i < measures.length; ++i) {
+            if (fieldPositionFoundIndex == -1) {
+                results[i] = formatMeasure(measures[i], new StringBuilder(), fpos);
+                if (fpos.getBeginIndex() != 0 || fpos.getEndIndex() != 0) {
+                    fieldPositionFoundIndex = i;    
+                }
+            } else {
+                results[i] = formatMeasure(measures[i], new StringBuilder(), dummyPos);
+            }
+        }
+        ListFormatter listFormatter = ListFormatter.getInstance(locale, 
+                length == FormatWidth.WIDE ? ListFormatter.Style.DURATION : ListFormatter.Style.DURATION_SHORT);
+        
+        // Fix up FieldPosition indexes if our field is found.
+        if (fieldPositionFoundIndex != -1) {
+            String listPattern = listFormatter.getPatternForNumItems(measures.length);
+            int positionInPattern = listPattern.indexOf("{" + fieldPositionFoundIndex + "}");
+            if (positionInPattern == -1) {
+                throw new IllegalStateException("Can't find position with ListFormatter.");
+            }
+            // Now we have to adjust our position in pattern
+            // based on the previous values.
+            for (int i = 0; i < fieldPositionFoundIndex; i++) {
+                positionInPattern += (results[i].length() - ("{" + i + "}").length());
+            }
+            fieldPosition.setBeginIndex(fpos.getBeginIndex() + positionInPattern);
+            fieldPosition.setEndIndex(fpos.getEndIndex() + positionInPattern);
+        }
+            
+        // This is safe because appendable is of type T.
+        try {
+            return (T) appendable.append(listFormatter.format((Object[]) results));
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+    
+    /**
+     * For two MeasureFormat objects, a and b, to be equal, <code>a.getClass().equals(b.getClass())</code>
+     * <code>a.equalsSameClass(b)</code> must be true.
+     * 
+     * @draft ICU 53
+     * @provisional
+     */
+    @Override
+    public final boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+        if (!(other instanceof MeasureFormat)) {
+            return false;
+        }
+        if (!getClass().equals(other.getClass())) {
+            return false;
+        }
+        return equalsSameClass((MeasureFormat) other);
+    }
+    
     /**
+     * @draft ICU 53
+     * @provisional
+     */
+    @Override
+    public int hashCode() {
+        return (numberFormat.hashCode() * 31 + locale.hashCode()) * 31 + length.hashCode();
+    }
+    
+    /**
+     * Returns true if this object is equal to other. The class of this and the class of other
+     * are guaranteed to be equal.
+     * 
+     * @deprecated For ICU internal use only.
      * @internal
-     * @deprecated This API is ICU internal only.
      */
-    protected MeasureFormat() {}
+    protected boolean equalsSameClass(MeasureFormat other) {
+        return objEquals(numberFormat,other.numberFormat)
+                && objEquals(locale, other.locale) && objEquals(length, other.length);        
+    }
+    
+    
+    /**
+     * Get the format width this instance is using.
+     * @draft ICU 53
+     * @provisional
+     */
+    public MeasureFormat.FormatWidth getWidth() {
+        return length;
+    }
+    
+    /**
+     * Get the locale of this instance.
+     * @draft ICU 53
+     * @provisional
+     */
+    public ULocale getLocale() {
+        return locale;
+    }
     
+    /**
+     * Get a copy of the number format.
+     * @draft ICU 53
+     * @provisional
+     */
+    public NumberFormat getNumberFormat() {
+        synchronized (numberFormat) {
+            return (NumberFormat) numberFormat.clone();
+        }
+    }
+
     /**
      * Return a formatter for CurrencyAmount objects in the given
      * locale.
@@ -55,4 +454,250 @@ public abstract class MeasureFormat extends UFormat {
     public static MeasureFormat getCurrencyFormat() {
         return getCurrencyFormat(ULocale.getDefault(Category.FORMAT));
     }
+    
+    MeasureFormat withLocale(ULocale locale) {
+        return MeasureFormat.getInstance(locale, getWidth());
+    }
+
+    MeasureFormat withNumberFormat(NumberFormat format) {
+        return new MeasureFormat(
+                this.locale,
+                this.length,
+                (NumberFormat) format.clone(),
+                this.rules,
+                this.unitToStyleToCountToFormat);
+    }
+    
+    private MeasureFormat(
+            ULocale locale,
+            FormatWidth width,
+            NumberFormat format,
+            PluralRules rules,
+            Map<MeasureUnit, EnumMap<FormatWidth, Map<String, PatternData>>> unitToStyleToCountToFormat) {
+        this.locale = locale;
+        this.length = width;
+        this.numberFormat = format;
+        this.rules = rules;
+        this.unitToStyleToCountToFormat = unitToStyleToCountToFormat;
+    }
+    
+    /**
+     * For backward compatibility only.
+     * @internal
+     * @deprecated
+     */
+    protected MeasureFormat() {
+        // Make compiler happy by setting final fields to null.
+        this.length = null;
+        this.locale = null;
+        this.numberFormat = null;
+        this.rules = null;
+        this.unitToStyleToCountToFormat = null;
+        
+    }
+    
+    /**
+     * Returns formatting data for all MeasureUnits except for currency ones.
+     */
+    private static Map<MeasureUnit, EnumMap<FormatWidth, Map<String, PatternData>>> loadLocaleData(
+            ULocale locale, PluralRules rules) {
+        Set<String> keywords = rules.getKeywords();
+        Map<MeasureUnit, EnumMap<FormatWidth, Map<String, PatternData>>> unitToStyleToCountToFormat
+                = new HashMap<MeasureUnit, EnumMap<FormatWidth, Map<String, PatternData>>>();
+        ICUResourceBundle resource = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, locale);
+        for (MeasureUnit unit : MeasureUnit.getAvailable()) {
+            // Currency data cannot be found here. Skip.
+            if (unit instanceof Currency) {
+                continue;
+            }
+            EnumMap<FormatWidth, Map<String, PatternData>> styleToCountToFormat = unitToStyleToCountToFormat.get(unit);
+            if (styleToCountToFormat == null) {
+                unitToStyleToCountToFormat.put(unit, styleToCountToFormat = new EnumMap<FormatWidth, Map<String, PatternData>>(FormatWidth.class));
+            }
+            for (FormatWidth styleItem : FormatWidth.values()) {
+                try {
+                    ICUResourceBundle unitTypeRes = resource.getWithFallback(styleItem.resourceKey);
+                    ICUResourceBundle unitsRes = unitTypeRes.getWithFallback(unit.getType());
+                    ICUResourceBundle oneUnitRes = unitsRes.getWithFallback(unit.getSubtype());
+                    Map<String, PatternData> countToFormat = styleToCountToFormat.get(styleItem);
+                    if (countToFormat == null) {
+                        styleToCountToFormat.put(styleItem, countToFormat = new HashMap<String, PatternData>());
+                    }
+                    // TODO(rocketman): Seems like we should be iterating over the bundles in
+                    // oneUnitRes instead of all the plural key words since most languages have
+                    // just 1 or 2 forms.
+                    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;
+    }
+    
+    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 static boolean objEquals(Object lhs, Object rhs) {
+        return lhs == null ? rhs == null : lhs.equals(rhs);
+        
+    }
+    
+    Object toTimeUnitProxy() {
+        return new MeasureProxy(locale, length, numberFormat, TIME_UNIT_FORMAT);
+    }
+    
+    Object toCurrencyProxy() {
+        return new MeasureProxy(locale, length, numberFormat, CURRENCY_FORMAT);
+    }
+    
+    private Object writeReplace() throws ObjectStreamException {
+        return new MeasureProxy(
+                locale, length, numberFormat, MEASURE_FORMAT);
+    }
+    
+    static class MeasureProxy implements Externalizable {
+        private static final long serialVersionUID = -6033308329886716770L;
+        
+        private ULocale locale;
+        private FormatWidth length;
+        private NumberFormat numberFormat;
+        private int subClass;
+        private HashMap<Object, Object> keyValues;
+
+        public MeasureProxy(
+                ULocale locale,
+                FormatWidth length,
+                NumberFormat numberFormat,
+                int subClass) {
+            this.locale = locale;
+            this.length = length;
+            this.numberFormat = numberFormat;
+            this.subClass = subClass;
+            this.keyValues = new HashMap<Object, Object>();
+        }
+
+        // Must have public constructor, to enable Externalizable
+        public MeasureProxy() {
+        }
+
+        public void writeExternal(ObjectOutput out) throws IOException {
+            out.writeByte(0); // version
+            out.writeObject(locale);
+            out.writeObject(length);
+            out.writeObject(numberFormat);
+            out.writeByte(subClass);
+            out.writeObject(keyValues);
+        }
+
+        @SuppressWarnings("unchecked")
+        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+            in.readByte(); // version.
+            locale = (ULocale) in.readObject();
+            if (locale == null) {
+                throw new InvalidObjectException("Missing locale.");
+            }
+            length = (FormatWidth) in.readObject();
+            if (length == null) {
+                throw new InvalidObjectException("Missing width.");
+            }
+            numberFormat = (NumberFormat) in.readObject();
+            if (numberFormat == null) {
+                throw new InvalidObjectException("Missing number format.");
+            }
+            subClass = in.readByte() & 0xFF;
+            
+            // This cast is safe because the serialized form of hashtable can have
+            // any object as the key and any object as the value.
+            keyValues = (HashMap<Object, Object>) in.readObject();
+            if (keyValues == null) {
+                throw new InvalidObjectException("Missing optional values map.");
+            }
+        }
+        
+        private TimeUnitFormat createTimeUnitFormat() throws InvalidObjectException {
+            int style;
+            if (length == FormatWidth.WIDE) {
+                style = TimeUnitFormat.FULL_NAME;
+            } else if (length == FormatWidth.SHORT) {
+                style = TimeUnitFormat.ABBREVIATED_NAME;
+            } else {
+                throw new InvalidObjectException("Bad width: " + length);
+            }
+            TimeUnitFormat result = new TimeUnitFormat(locale, style);
+            result.setNumberFormat(numberFormat);
+            return result;
+        }
+
+        private Object readResolve() throws ObjectStreamException {
+            switch (subClass) {
+            case MEASURE_FORMAT:
+                return MeasureFormat.getInstance(locale, length, numberFormat);
+            case TIME_UNIT_FORMAT:
+                return createTimeUnitFormat();
+            case CURRENCY_FORMAT:
+                return new CurrencyFormat(locale);
+            default:
+                throw new InvalidObjectException("Unknown subclass: " + subClass);
+            }
+        }
+    }
 }
index 8cf2e5d1cab86a7e6e52afcbdd74420ae7201fda..130fb25bdf244643322b7f5cdfd840719e125db8 100644 (file)
@@ -6,6 +6,7 @@
  */
 package com.ibm.icu.text;
 
+import java.io.ObjectStreamException;
 import java.text.FieldPosition;
 import java.text.ParseException;
 import java.text.ParsePosition;
@@ -18,6 +19,7 @@ import java.util.Set;
 import java.util.TreeMap;
 
 import com.ibm.icu.impl.ICUResourceBundle;
+import com.ibm.icu.util.Measure;
 import com.ibm.icu.util.TimeUnit;
 import com.ibm.icu.util.TimeUnitAmount;
 import com.ibm.icu.util.ULocale;
@@ -76,6 +78,23 @@ public class TimeUnitFormat extends MeasureFormat {
 
     private static final long serialVersionUID = -3707773153184971529L;
   
+    // These fields are supposed to be the same as the fields in mf. They
+    // are here for serialization backward compatibility and to support parsing.
+    private NumberFormat format;
+    private ULocale locale;
+    private int style;
+     
+    // We use this field in lieu of the super class because the super class
+    // is immutable while this class is mutable. The contents of the super class
+    // is an empty shell. Every public method of the super class is overridden to
+    // delegate to this field. Each time this object mutates, it replaces this field with
+    // a new immutable instance.
+    private transient MeasureFormat mf;
+    
+    private transient Map<TimeUnit, Map<String, Object[]>> timeUnitToCountToPatterns;
+    private transient PluralRules pluralRules;
+    private transient boolean isReady;
+    
     private static final String DEFAULT_PATTERN_FOR_SECOND = "{0} s";
     private static final String DEFAULT_PATTERN_FOR_MINUTE = "{0} min";
     private static final String DEFAULT_PATTERN_FOR_HOUR = "{0} h";
@@ -84,22 +103,15 @@ public class TimeUnitFormat extends MeasureFormat {
     private static final String DEFAULT_PATTERN_FOR_MONTH = "{0} m";
     private static final String DEFAULT_PATTERN_FOR_YEAR = "{0} y";
 
-    private NumberFormat format;
-    private ULocale locale;
-    private transient Map<TimeUnit, Map<String, Object[]>> timeUnitToCountToPatterns;
-    private transient PluralRules pluralRules;
-    private transient boolean isReady;
-    private int style;
-
     /**
      * Create empty format using full name style, for example, "hours". 
      * Use setLocale and/or setFormat to modify.
      * @stable ICU 4.0
      */
     public TimeUnitFormat() {
+        mf = MeasureFormat.getInstance(ULocale.getDefault(), FormatWidth.WIDE);
         isReady = false;
         style = FULL_NAME;
-
     }
 
     /**
@@ -132,10 +144,17 @@ public class TimeUnitFormat extends MeasureFormat {
         if (style < FULL_NAME || style >= TOTAL_STYLES) {
             throw new IllegalArgumentException("style should be either FULL_NAME or ABBREVIATED_NAME style");
         }
+        mf = MeasureFormat.getInstance(
+                locale, style == FULL_NAME ? FormatWidth.WIDE : FormatWidth.SHORT);
         this.style = style;
         this.locale = locale;
         isReady = false;
     }
+    
+    private TimeUnitFormat(ULocale locale, int style, NumberFormat numberFormat) {
+        this(locale, style);
+        setNumberFormat(numberFormat);
+    }
 
     /**
      * Create TimeUnitFormat given a Locale and a formatting style.
@@ -152,7 +171,8 @@ public class TimeUnitFormat extends MeasureFormat {
      * @stable ICU 4.0
      */
     public TimeUnitFormat setLocale(ULocale locale) {
-        if ( locale != this.locale ) {
+        if (locale != this.locale){ 
+            mf = mf.withLocale(locale);
             this.locale = locale;
             isReady = false;
         }
@@ -180,15 +200,17 @@ public class TimeUnitFormat extends MeasureFormat {
         if (format == this.format) {
             return this;
         }
-        if ( format == null ) {
-            if ( locale == null ) {
+        if (format == null) {
+            if (locale == null) {
                 isReady = false;
-                return this;
+                mf = mf.withLocale(ULocale.getDefault());
             } else {
                 this.format = NumberFormat.getNumberInstance(locale);
+                mf = mf.withNumberFormat(this.format);
             }
         } else {
             this.format = format;
+            mf = mf.withNumberFormat(this.format);
         }
         return this;
     }
@@ -201,25 +223,7 @@ public class TimeUnitFormat extends MeasureFormat {
      */
     public StringBuffer format(Object obj, StringBuffer toAppendTo,
             FieldPosition pos) {
-        if ( !(obj instanceof TimeUnitAmount) ) {
-            throw new IllegalArgumentException(
-                    "cannot format a non TimeUnitAmount object");
-        }
-        if (!isReady) {
-            setup();
-        }
-        TimeUnitAmount amount = (TimeUnitAmount) obj;
-        Map<String, Object[]> countToPattern = timeUnitToCountToPatterns.get(amount.getTimeUnit());
-        String formattedNumber = format.format(amount.getNumber());
-        double doubleNumber = amount.getNumber().doubleValue();
-        String count;
-        if (format instanceof DecimalFormat) {
-            count = pluralRules.select(((DecimalFormat) format).getFixedDecimal(doubleNumber));
-        } else {
-            count = pluralRules.select(doubleNumber);
-        }
-        MessageFormat pattern = (MessageFormat)(countToPattern.get(count))[style];
-        return pattern.format(new Object[]{formattedNumber}, toAppendTo, pos);
+        return mf.format(obj, toAppendTo, pos);
     }
     
     /**
@@ -227,7 +231,8 @@ public class TimeUnitFormat extends MeasureFormat {
      * @see java.text.Format#parseObject(java.lang.String, java.text.ParsePosition)
      * @stable ICU 4.0
      */
-    public Object parseObject(String source, ParsePosition pos) {
+    @Override
+    public TimeUnitAmount parseObject(String source, ParsePosition pos) {
         if (!isReady) {
             setup();
         }
@@ -313,13 +318,6 @@ public class TimeUnitFormat extends MeasureFormat {
         }
     }
     
-    
-    /*
-     * Initialize locale, number formatter, plural rules, and
-     * time units patterns.
-     * Initially, we are storing all of these as MessageFormats.
-     * I think it might actually be simpler to make them Decimal Formats later.
-     */
     private void setup() {
         if (locale == null) {
             if (format != null) {
@@ -340,174 +338,230 @@ public class TimeUnitFormat extends MeasureFormat {
     }
     
     private void setup(String resourceKey, Map<TimeUnit, Map<String, Object[]>> timeUnitToCountToPatterns,
-                       int style, Set<String> pluralKeywords) {
-        // fill timeUnitToCountToPatterns from resource file
-        try {
-            ICUResourceBundle resource = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, locale);
-            ICUResourceBundle unitsRes = resource.getWithFallback(resourceKey);
-            int size = unitsRes.getSize();
-            for ( int index = 0; index < size; ++index) {
-                String timeUnitName = unitsRes.get(index).getKey();
-                TimeUnit timeUnit = null;
-                if ( timeUnitName.equals("year") ) {
-                    timeUnit = TimeUnit.YEAR;
-                } else if ( timeUnitName.equals("month") ) {
-                    timeUnit = TimeUnit.MONTH;
-                } else if ( timeUnitName.equals("day") ) {
-                    timeUnit = TimeUnit.DAY;
-                } else if ( timeUnitName.equals("hour") ) {
-                    timeUnit = TimeUnit.HOUR;
-                } else if ( timeUnitName.equals("minute") ) {
-                    timeUnit = TimeUnit.MINUTE;
-                } else if ( timeUnitName.equals("second") ) {
-                    timeUnit = TimeUnit.SECOND;
-                } else if ( timeUnitName.equals("week") ) {
-                    timeUnit = TimeUnit.WEEK;
-                } else {
-                    continue;
-                }
-                ICUResourceBundle oneUnitRes = unitsRes.getWithFallback(timeUnitName);
-                int count = oneUnitRes.getSize();
-                Map<String, Object[]> countToPatterns = timeUnitToCountToPatterns.get(timeUnit);
-                if (countToPatterns ==  null) {
-                    countToPatterns = new TreeMap<String, Object[]>();
-                    timeUnitToCountToPatterns.put(timeUnit, countToPatterns);
-                } 
-                for ( int pluralIndex = 0; pluralIndex < count; ++pluralIndex) {
-                    String pluralCount = oneUnitRes.get(pluralIndex).getKey();
-                    if (!pluralKeywords.contains(pluralCount))
-                        continue;
-                    String pattern = oneUnitRes.get(pluralIndex).getString();
-                    final MessageFormat messageFormat = new MessageFormat(pattern, locale);
-                    // save both full name and abbreviated name in one table
-                    // is good space-wise, but it degrades performance, 
-                    // since it needs to check whether the needed space 
-                    // is already allocated or not.
-                    Object[] pair = countToPatterns.get(pluralCount);
-                    if (pair == null) {
-                        pair = new Object[2];
-                        countToPatterns.put(pluralCount, pair);
-                    } 
-                    pair[style] = messageFormat;
-                }
-            }
-        } catch ( MissingResourceException e ) {
-        }
-
-        // there should be patterns for each plural rule in each time unit.
-        // For each time unit, 
-        //     for each plural rule, following is unit pattern fall-back rule:
-        //         ( for example: "one" hour )
-        //         look for its unit pattern in its locale tree.
-        //         if pattern is not found in its own locale, such as de_DE,
-        //         look for the pattern in its parent, such as de,
-        //         keep looking till found or till root.
-        //         if the pattern is not found in root either,
-        //         fallback to plural count "other",
-        //         look for the pattern of "other" in the locale tree:
-        //         "de_DE" to "de" to "root".
-        //         If not found, fall back to value of 
-        //         static variable DEFAULT_PATTERN_FOR_xxx, such as "{0} h". 
-        //
-        // Following is consistency check to create pattern for each
-        // plural rule in each time unit using above fall-back rule.
-        //
-        final TimeUnit[] timeUnits = TimeUnit.values();
-        Set<String> keywords = pluralRules.getKeywords();
-        for ( int i = 0; i < timeUnits.length; ++i ) {
-            // for each time unit, 
-            // get all the patterns for each plural rule in this locale.
-            final TimeUnit timeUnit = timeUnits[i];
-            Map<String, Object[]> countToPatterns = timeUnitToCountToPatterns.get(timeUnit);
-            if (countToPatterns == null) {
-                countToPatterns = new TreeMap<String, Object[]>();
-                timeUnitToCountToPatterns.put(timeUnit, countToPatterns);
-            }
-            for (String pluralCount : keywords) {
-                if ( countToPatterns.get(pluralCount) == null ||
-                     countToPatterns.get(pluralCount)[style] == null ) {
-                    // look through parents
-                    searchInTree(resourceKey, style, timeUnit, pluralCount, pluralCount, countToPatterns);
-                }
-            }
-        }
+            int style, Set<String> pluralKeywords) {
+// fill timeUnitToCountToPatterns from resource file
+try {
+ ICUResourceBundle resource = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, locale);
+ ICUResourceBundle unitsRes = resource.getWithFallback(resourceKey);
+ int size = unitsRes.getSize();
+ for ( int index = 0; index < size; ++index) {
+     String timeUnitName = unitsRes.get(index).getKey();
+     TimeUnit timeUnit = null;
+     if ( timeUnitName.equals("year") ) {
+         timeUnit = TimeUnit.YEAR;
+     } else if ( timeUnitName.equals("month") ) {
+         timeUnit = TimeUnit.MONTH;
+     } else if ( timeUnitName.equals("day") ) {
+         timeUnit = TimeUnit.DAY;
+     } else if ( timeUnitName.equals("hour") ) {
+         timeUnit = TimeUnit.HOUR;
+     } else if ( timeUnitName.equals("minute") ) {
+         timeUnit = TimeUnit.MINUTE;
+     } else if ( timeUnitName.equals("second") ) {
+         timeUnit = TimeUnit.SECOND;
+     } else if ( timeUnitName.equals("week") ) {
+         timeUnit = TimeUnit.WEEK;
+     } else {
+         continue;
+     }
+     ICUResourceBundle oneUnitRes = unitsRes.getWithFallback(timeUnitName);
+     int count = oneUnitRes.getSize();
+     Map<String, Object[]> countToPatterns = timeUnitToCountToPatterns.get(timeUnit);
+     if (countToPatterns ==  null) {
+         countToPatterns = new TreeMap<String, Object[]>();
+         timeUnitToCountToPatterns.put(timeUnit, countToPatterns);
+     }
+     for ( int pluralIndex = 0; pluralIndex < count; ++pluralIndex) {
+         String pluralCount = oneUnitRes.get(pluralIndex).getKey();
+         if (!pluralKeywords.contains(pluralCount))
+             continue;
+         String pattern = oneUnitRes.get(pluralIndex).getString();
+         final MessageFormat messageFormat = new MessageFormat(pattern, locale);
+         // save both full name and abbreviated name in one table
+         // is good space-wise, but it degrades performance,
+         // since it needs to check whether the needed space
+         // is already allocated or not.
+         Object[] pair = countToPatterns.get(pluralCount);
+         if (pair == null) {
+             pair = new Object[2];
+             countToPatterns.put(pluralCount, pair);
+         }
+         pair[style] = messageFormat;
+     }
+ }
+} catch ( MissingResourceException e ) {
+}
+// there should be patterns for each plural rule in each time unit.
+// For each time unit,
+//     for each plural rule, following is unit pattern fall-back rule:
+//         ( for example: "one" hour )
+//         look for its unit pattern in its locale tree.
+//         if pattern is not found in its own locale, such as de_DE,
+//         look for the pattern in its parent, such as de,
+//         keep looking till found or till root.
+//         if the pattern is not found in root either,
+//         fallback to plural count "other",
+//         look for the pattern of "other" in the locale tree:
+//         "de_DE" to "de" to "root".
+//         If not found, fall back to value of
+//         static variable DEFAULT_PATTERN_FOR_xxx, such as "{0} h".
+//
+// Following is consistency check to create pattern for each
+// plural rule in each time unit using above fall-back rule.
+//
+final TimeUnit[] timeUnits = TimeUnit.values();
+Set<String> keywords = pluralRules.getKeywords();
+for ( int i = 0; i < timeUnits.length; ++i ) {
+ // for each time unit,
+ // get all the patterns for each plural rule in this locale.
+ final TimeUnit timeUnit = timeUnits[i];
+ Map<String, Object[]> countToPatterns = timeUnitToCountToPatterns.get(timeUnit);
+ if (countToPatterns == null) {
+     countToPatterns = new TreeMap<String, Object[]>();
+     timeUnitToCountToPatterns.put(timeUnit, countToPatterns);
+ }
+ for (String pluralCount : keywords) {
+     if ( countToPatterns.get(pluralCount) == null ||
+          countToPatterns.get(pluralCount)[style] == null ) {
+         // look through parents
+         searchInTree(resourceKey, style, timeUnit, pluralCount, pluralCount, countToPatterns);
+     }
+ }
+}
+}
+// srcPluralCount is the original plural count on which the pattern is
+// searched for.
+// searchPluralCount is the fallback plural count.
+// For example, to search for pattern for ""one" hour",
+// "one" is the srcPluralCount,
+// if the pattern is not found even in root, fallback to
+// using patterns of plural count "other",
+// then, "other" is the searchPluralCount.
+private void searchInTree(String resourceKey, int styl,
+                   TimeUnit timeUnit, String srcPluralCount,
+                   String searchPluralCount, Map<String, Object[]> countToPatterns) {
+ULocale parentLocale=locale;
+String srcTimeUnitName = timeUnit.toString();
+while ( parentLocale != null ) {
+ try {
+     // look for pattern for srcPluralCount in locale tree
+     ICUResourceBundle unitsRes = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, parentLocale);
+     unitsRes = unitsRes.getWithFallback(resourceKey);
+     ICUResourceBundle oneUnitRes = unitsRes.getWithFallback(srcTimeUnitName);
+     String pattern = oneUnitRes.getStringWithFallback(searchPluralCount);
+     final MessageFormat messageFormat = new MessageFormat(pattern, locale);
+     Object[] pair = countToPatterns.get(srcPluralCount);
+     if (pair == null) {
+         pair = new Object[2];
+         countToPatterns.put(srcPluralCount, pair);
+     }
+     pair[styl] = messageFormat;
+     return;
+ } catch ( MissingResourceException e ) {
+ }
+ parentLocale=parentLocale.getFallback();
+}
+// if no unitsShort resource was found even after fallback to root locale
+// then search the units resource fallback from the current level to root
+if ( parentLocale == null && resourceKey.equals("unitsShort") ) {
+ searchInTree("units", styl, timeUnit, srcPluralCount, searchPluralCount, countToPatterns);
+ if ( countToPatterns != null &&
+         countToPatterns.get(srcPluralCount) != null &&
+         countToPatterns.get(srcPluralCount)[styl] != null ) {
+     return;
+ }
+}
+// if not found the pattern for this plural count at all,
+// fall-back to plural count "other"
+if ( searchPluralCount.equals("other") ) {
+ // set default fall back the same as the resource in root
+ MessageFormat messageFormat = null;
+ if ( timeUnit == TimeUnit.SECOND ) {
+     messageFormat = new MessageFormat(DEFAULT_PATTERN_FOR_SECOND, locale);
+ } else if ( timeUnit == TimeUnit.MINUTE ) {
+     messageFormat = new MessageFormat(DEFAULT_PATTERN_FOR_MINUTE, locale);
+ } else if ( timeUnit == TimeUnit.HOUR ) {
+     messageFormat = new MessageFormat(DEFAULT_PATTERN_FOR_HOUR, locale);
+ } else if ( timeUnit == TimeUnit.WEEK ) {
+     messageFormat = new MessageFormat(DEFAULT_PATTERN_FOR_WEEK, locale);
+ } else if ( timeUnit == TimeUnit.DAY ) {
+     messageFormat = new MessageFormat(DEFAULT_PATTERN_FOR_DAY, locale);
+ } else if ( timeUnit == TimeUnit.MONTH ) {
+     messageFormat = new MessageFormat(DEFAULT_PATTERN_FOR_MONTH, locale);
+ } else if ( timeUnit == TimeUnit.YEAR ) {
+     messageFormat = new MessageFormat(DEFAULT_PATTERN_FOR_YEAR, locale);
+ }
+ Object[] pair = countToPatterns.get(srcPluralCount);
+ if (pair == null) {
+     pair = new Object[2];
+     countToPatterns.put(srcPluralCount, pair);
+ }
+ pair[styl] = messageFormat;
+} else {
+ // fall back to rule "other", and search in parents
+ searchInTree(resourceKey, styl, timeUnit, srcPluralCount, "other", countToPatterns);
+}
+}
+    
+    // boilerplate code to make TimeUnitFormat otherwise follow the contract of
+    // MeasureFormat
+    
+    @Override
+    public String formatMeasures(Measure... measures) {
+        return mf.formatMeasures(measures);
     }
-
-
-
-    // srcPluralCount is the original plural count on which the pattern is
-    // searched for.
-    // searchPluralCount is the fallback plural count.
-    // For example, to search for pattern for ""one" hour",
-    // "one" is the srcPluralCount,
-    // if the pattern is not found even in root, fallback to 
-    // using patterns of plural count "other", 
-    // then, "other" is the searchPluralCount.
-    private void searchInTree(String resourceKey, int styl,
-                              TimeUnit timeUnit, String srcPluralCount,
-                              String searchPluralCount, Map<String, Object[]> countToPatterns) {
-        ULocale parentLocale=locale;
-        String srcTimeUnitName = timeUnit.toString();
-        while ( parentLocale != null ) {
-            try {
-                // look for pattern for srcPluralCount in locale tree
-                ICUResourceBundle unitsRes = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, parentLocale);
-                unitsRes = unitsRes.getWithFallback(resourceKey);
-                ICUResourceBundle oneUnitRes = unitsRes.getWithFallback(srcTimeUnitName);
-                String pattern = oneUnitRes.getStringWithFallback(searchPluralCount);
-                final MessageFormat messageFormat = new MessageFormat(pattern, locale);
-                Object[] pair = countToPatterns.get(srcPluralCount);
-                if (pair == null) {
-                    pair = new Object[2];
-                    countToPatterns.put(srcPluralCount, pair);
-                }
-                pair[styl] = messageFormat;
-                return;
-            } catch ( MissingResourceException e ) {
-            }
-            parentLocale=parentLocale.getFallback();
-        }
-
-        // if no unitsShort resource was found even after fallback to root locale
-        // then search the units resource fallback from the current level to root
-        if ( parentLocale == null && resourceKey.equals("unitsShort") ) {
-            searchInTree("units", styl, timeUnit, srcPluralCount, searchPluralCount, countToPatterns);
-            if ( countToPatterns != null &&
-                    countToPatterns.get(srcPluralCount) != null &&
-                    countToPatterns.get(srcPluralCount)[styl] != null ) {
-                return;
-            }
-        }
-
-        // if not found the pattern for this plural count at all,
-        // fall-back to plural count "other"
-        if ( searchPluralCount.equals("other") ) {
-            // set default fall back the same as the resource in root
-            MessageFormat messageFormat = null;
-            if ( timeUnit == TimeUnit.SECOND ) {
-                messageFormat = new MessageFormat(DEFAULT_PATTERN_FOR_SECOND, locale);
-            } else if ( timeUnit == TimeUnit.MINUTE ) {
-                messageFormat = new MessageFormat(DEFAULT_PATTERN_FOR_MINUTE, locale);
-            } else if ( timeUnit == TimeUnit.HOUR ) {
-                messageFormat = new MessageFormat(DEFAULT_PATTERN_FOR_HOUR, locale);
-            } else if ( timeUnit == TimeUnit.WEEK ) {
-                messageFormat = new MessageFormat(DEFAULT_PATTERN_FOR_WEEK, locale);
-            } else if ( timeUnit == TimeUnit.DAY ) {
-                messageFormat = new MessageFormat(DEFAULT_PATTERN_FOR_DAY, locale);
-            } else if ( timeUnit == TimeUnit.MONTH ) {
-                messageFormat = new MessageFormat(DEFAULT_PATTERN_FOR_MONTH, locale);
-            } else if ( timeUnit == TimeUnit.YEAR ) {
-                messageFormat = new MessageFormat(DEFAULT_PATTERN_FOR_YEAR, locale);
-            }
-            Object[] pair = countToPatterns.get(srcPluralCount);
-            if (pair == null) {
-                pair = new Object[2];
-                countToPatterns.put(srcPluralCount, pair);
-            }
-            pair[styl] = messageFormat;
-        } else {
-            // fall back to rule "other", and search in parents
-            searchInTree(resourceKey, styl, timeUnit, srcPluralCount, "other", countToPatterns);
-        }
+    
+    @Override
+    public <T extends Appendable> T formatMeasure(
+            Measure measure, T appendable, FieldPosition fieldPosition) {
+        return mf.formatMeasure(measure, appendable, fieldPosition);
+    }
+    
+    @Override
+    public <T extends Appendable> T formatMeasures(
+            T appendable, FieldPosition fieldPosition, Measure... measures) {
+        return mf.formatMeasures(appendable, fieldPosition, measures);
+    }
+    
+    @Override
+    public MeasureFormat.FormatWidth getWidth() {
+        return mf.getWidth();
+    }
+    
+    @Override
+    public ULocale getLocale() {
+        return mf.getLocale();
+    }
+    
+    @Override
+    public NumberFormat getNumberFormat() {
+        return mf.getNumberFormat();
+    }
+    
+    // End boilerplate.
+    
+    // equals / hashcode
+    
+    @Override
+    public int hashCode() {
+        return getLocale().hashCode() + 911247101;
+    }
+    
+    @Override
+    protected boolean equalsSameClass(MeasureFormat other) {
+        return getLocale().equals(other.getLocale());
+    }
+    
+    // End equals / hashcode
+    
+    // Serialization
+    
+    private Object writeReplace() throws ObjectStreamException {
+        return mf.toTimeUnitProxy();
+    }
+    
+    // Preserve backward serialize backward compatibility.
+    private Object readResolve() throws ObjectStreamException {
+        return new TimeUnitFormat(locale, style, format);
     }
 }
index c353e09381d41728b990b00ad96f53875ce15d87..e2df269e90bf65e41cd07d3b2559913ce059ed60 100644 (file)
@@ -7,7 +7,6 @@
 package com.ibm.icu.util;
 
 import java.io.ObjectStreamException;
-import java.io.Serializable;
 import java.lang.ref.SoftReference;
 import java.text.ParsePosition;
 import java.util.ArrayList;
@@ -53,7 +52,7 @@ import com.ibm.icu.util.ULocale.Category;
  * @author Alan Liu
  * @stable ICU 2.2
  */
-public class Currency extends MeasureUnit implements Serializable {
+public class Currency extends MeasureUnit {
     private static final long serialVersionUID = -5839973855554750484L;
     private static final boolean DEBUG = ICUDebug.enabled("currency");
 
@@ -245,7 +244,7 @@ public class Currency extends MeasureUnit implements Serializable {
             throw new IllegalArgumentException(
                     "The input currency code is not 3-letter alphabetic code.");
         }
-        return (Currency) MeasureUnit.addUnit("currency", theISOCode.toUpperCase(Locale.ENGLISH), CURRENCY_FACTORY);
+        return (Currency) MeasureUnit.internalGetInstance("currency", theISOCode.toUpperCase(Locale.ENGLISH));
     }
     
     
@@ -784,7 +783,7 @@ public class Currency extends MeasureUnit implements Serializable {
 
         // isoCode is kept for readResolve() and Currency class no longer
         // use it. So this statement actually does not have any effect.
-        isoCode = code; 
+        isoCode = theISOCode;
     }
 
     // POW10[i] = 10^i
@@ -914,6 +913,7 @@ public class Currency extends MeasureUnit implements Serializable {
     private final String isoCode;
 
     private Object readResolve() throws ObjectStreamException {
+        // The old isoCode field used to determine the currency.
         return Currency.getInstance(isoCode);
     }
 }
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/util/FormatWidth.java b/icu4j/main/classes/core/src/com/ibm/icu/util/FormatWidth.java
deleted file mode 100644 (file)
index 096757c..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- *******************************************************************************
- * Copyright (C) 2013, International Business Machines Corporation and         *
- * others. All Rights Reserved.                                                *
- *******************************************************************************
- */
-package com.ibm.icu.util;
-
-/**
- * General purpose formatting width enum.
- * @internal
- * @deprecated This API is ICU internal only.
- */
-public enum FormatWidth {
-    /**
-     * @internal
-     * @deprecated This API is ICU internal only.
-     */
-    WIDE("units"), 
-    
-    /**
-     * @internal
-     * @deprecated This API is ICU internal only.
-     */
-    SHORT("unitsShort"), 
-    
-    /**
-     * @internal
-     * @deprecated This API is ICU internal only.
-     */
-    NARROW("unitsNarrow");
-
-    /**
-     * @internal
-     * @deprecated internal use
-     */
-    public final String resourceKey;
-
-    private FormatWidth(String resourceKey) {
-        this.resourceKey = resourceKey;
-    }
-}
\ No newline at end of file
index 92cc5e160d26f1c2ceef7be8dc861b39645a8dc8..a9937f2cba81a30fbf1091c3c90d27b1db57c1a2 100644 (file)
@@ -14,13 +14,13 @@ package com.ibm.icu.util;
 /**
  * An amount of a specified unit, consisting of a Number and a Unit.
  * For example, a length measure consists of a Number and a length
- * unit, such as feet or meters.  This is an abstract class.
- * Subclasses specify a concrete Unit type.
+ * unit, such as feet or meters.
  *
  * <p>Measure objects are parsed and formatted by subclasses of
  * MeasureFormat.
  *
  * <p>Measure objects are immutable. All subclasses must guarantee that.
+ * (However, subclassing is discouraged.)
  *
  * @see java.lang.Number
  * @see com.ibm.icu.util.MeasureUnit
@@ -31,7 +31,6 @@ package com.ibm.icu.util;
 public class Measure {
     
     private final Number number;
-
     private final MeasureUnit unit;
 
     /**
@@ -54,14 +53,14 @@ public class Measure {
      * @stable ICU 3.0
      */
     public boolean equals(Object obj) {
-        if (obj == null) return false;
-        if (obj == this) return true;
-        try {
-            Measure m = (Measure) obj;
-            return unit.equals(m.unit) && numbersEqual(number, m.number);
-        } catch (ClassCastException e) {
+        if (obj == this) {
+            return true;
+        }
+        if (!(obj instanceof Measure)) {
             return false;
         }
+        Measure m = (Measure) obj;
+        return unit.equals(m.unit) && numbersEqual(number, m.number);
     }
     
     /*
@@ -87,7 +86,7 @@ public class Measure {
      * @stable ICU 3.0
      */
     public int hashCode() {
-        return number.hashCode() ^ unit.hashCode();
+        return 31 * Double.valueOf(number.doubleValue()).hashCode() + unit.hashCode();
     }
 
     /**
index 08b8c761f33049e308fe92e2130a0d56e18af3f6..595437a6e26b10eb692e7c7540530bfd59d3a1e9 100644 (file)
@@ -20,22 +20,26 @@ import java.util.HashSet;
 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.
+ * coupled with a numeric amount to produce a Measure. MeasureUnit objects are immutable.
+ * All subclasses must guarantee that. (However, subclassing is discouraged.)
+
  *
  * @see com.ibm.icu.util.Measure
  * @author Alan Liu
  * @stable ICU 3.0
  */
-public class MeasureUnit implements Comparable<MeasureUnit>, Serializable {
+public class MeasureUnit implements Serializable {
     private static final long serialVersionUID = -1839973855554750484L;
-
+    
+    // Used to pre-fill the cache. These same constants appear in MeasureFormat too.
+    private static final String[] unitKeys = new String[]{"units", "unitsShort", "unitsNarrow"};
+    
     private static final Map<String, Map<String,MeasureUnit>> cache 
     = new HashMap<String, Map<String,MeasureUnit>>();
 
@@ -59,34 +63,130 @@ public class MeasureUnit implements Comparable<MeasureUnit>, Serializable {
         this.type = type;
         this.code = code;
     }
+    
+    /**
+     * Get the type, such as "length"
+     * 
+     * @draft ICU 53
+     * @provisional
+     */
+    public String getType() {
+        return type;
+    }
+    
 
     /**
-     * Create an instance of a measurement unit.
-     * <p>
-     * Warning: Currently, the values of the parameters depend on the structure of
-     * ICU resource bundles. Do not use this function unless you know what you are
-     * doing.
+     * Get the subType, such as “foot”.
+     *
+     * @draft ICU 53
+     * @provisional
+     */
+    public String getSubtype() {
+        return code;
+    }
+    
+    
+
+    /**
+     * @draft ICU 53
+     * @provisional
+     */
+    @Override
+    public int hashCode() {
+        return 31 * type.hashCode() + code.hashCode();
+    }
+    
+    /**
+     * @draft ICU 53
+     * @provisional
+     */
+    @Override
+    public boolean equals(Object rhs) {
+        if (rhs == this) {
+            return true;
+        }
+        if (!(rhs instanceof MeasureUnit)) {
+            return false;
+        }
+        MeasureUnit c = (MeasureUnit) rhs;
+        return type.equals(c.type) && code.equals(c.code);
+    }
+    
+    /**
+     * @draft ICU 53
+     * @provisional
+     */
+    @Override
+    public String toString() {
+        return type + "-" + code;
+    }
+    
+    /**
+     * Get all of the available units' types.
      * 
-     * @param type the type, such as "length"
-     * @param code the code, such as "meter"
-     * @return the unit.
+     * @draft ICU 53
+     * @provisional
+     */
+    public synchronized static Set<String> getAvailableTypes() {
+        return new HashSet<String>(cache.keySet());
+    }
+
+    /**
+     * For the given type, return the available units.
+     * @param type the type
+     * @return the available units for type
+     * @draft ICU 53
+     * @provisional
+     */
+    public synchronized static Collection<MeasureUnit> getAvailable(String type) {
+        Map<String, MeasureUnit> units = cache.get(type);
+        return units == null ? null : new ArrayList<MeasureUnit>(units.values());
+    }
+
+    /**
+     * Get all of the available units.
+     *
+     * @draft ICU 53
+     * @provisional
+     */
+    public synchronized static Set<MeasureUnit> getAvailable() {
+        Set<MeasureUnit> result = new HashSet<MeasureUnit>();
+        for (String type : new HashSet<String>(MeasureUnit.getAvailableTypes())) {
+            for (MeasureUnit unit : MeasureUnit.getAvailable(type)) {
+                result.add(unit);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Create a MeasureUnit instance (creates a singleton instance).
+     * <p>
+     * Normally this method should not be used, since there will be no formatting data
+     * available for it, and it may not be returned by getAvailable().
+     * However, for special purposes (such as CLDR tooling), it is available.
+     *
      * @internal
      * @deprecated This API is ICU internal only.
      */
-    public static MeasureUnit getInstance(String type, String code) {
-        synchronized (MeasureUnit.class){ 
-            Map<String, MeasureUnit> tmp = cache.get(type);
-            if (tmp != null) {
-                MeasureUnit result = tmp.get(code);
-                if (result != null) {
-                    return result;
-                }
+    public static MeasureUnit internalGetInstance(String type, String code) {
+        if (type == null || code == null) {
+            throw new NullPointerException("Type and code must be non-null");
+        }
+        if (!"currency".equals(type)) {
+            if (!ASCII.containsAll(type) || !ASCII_HYPHEN.containsAll(code)) {
+                throw new IllegalArgumentException("The type or code are invalid.");
             }
         }
-        if (type == null || !ASCII.containsAll(type) || code == null || !ASCII_HYPHEN.containsAll(code)) {
-            throw new NullPointerException("The type or code are invalid.");
+        Factory factory;
+        if ("currency".equals(type)) {
+            factory = CURRENCY_FACTORY;
+        } else if ("duration".equals(type)) {
+            factory = TIMEUNIT_FACTORY;
+        } else {
+            factory = UNIT_FACTORY;
         }
-        return MeasureUnit.addUnit(type, code, UNIT_FACTORY);
+        return MeasureUnit.addUnit(type, code, factory);
     }
 
     static final UnicodeSet ASCII = new UnicodeSet('a', 'z').freeze();
@@ -111,10 +211,16 @@ public class MeasureUnit implements Comparable<MeasureUnit>, Serializable {
     };
 
     static Factory CURRENCY_FACTORY = new Factory() {
-        public MeasureUnit create(String type, String code) {
+        public MeasureUnit create(String unusedType, String code) {
             return new Currency(code);
         }
     };
+    
+    static Factory TIMEUNIT_FACTORY = new Factory() {
+        public MeasureUnit create(String type, String code) {
+           return new TimeUnit(type, code);
+        }
+    };
 
 
     //    /**
@@ -144,9 +250,9 @@ public class MeasureUnit implements Comparable<MeasureUnit>, Serializable {
          *                }
          */
         ICUResourceBundle resource = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, "en");
-        for (FormatWidth key : FormatWidth.values()) {
+        for (String key : unitKeys) {
             try {
-                ICUResourceBundle unitsTypeRes = resource.getWithFallback(key.resourceKey);
+                ICUResourceBundle unitsTypeRes = resource.getWithFallback(key);
                 int size = unitsTypeRes.getSize();
                 for ( int index = 0; index < size; ++index) {
                     UResourceBundle unitsRes = unitsTypeRes.get(index);
@@ -155,7 +261,7 @@ public class MeasureUnit implements Comparable<MeasureUnit>, Serializable {
                     for ( int index2 = 0; index2 < unitsSize; ++index2) {
                         ICUResourceBundle unitNameRes = (ICUResourceBundle)unitsRes.get(index2);
                         if (unitNameRes.get("other") != null) {
-                            addUnit(type, unitNameRes.getKey(), UNIT_FACTORY);
+                            internalGetInstance(type, unitNameRes.getKey());
                         }
                     }
                 }
@@ -171,7 +277,7 @@ public class MeasureUnit implements Comparable<MeasureUnit>, Serializable {
                     ICUResourceBundle.ICU_DATA_CLASS_LOADER);
             UResourceBundle codeMap = bundle.get("codeMap");
             for (Enumeration<String> it = codeMap.getKeys(); it.hasMoreElements();) {
-                MeasureUnit.addUnit("currency", it.nextElement(), CURRENCY_FACTORY);
+                MeasureUnit.internalGetInstance("currency", it.nextElement());
             }
         } catch (MissingResourceException e) {
             // fall through
@@ -198,204 +304,109 @@ public class MeasureUnit implements Comparable<MeasureUnit>, Serializable {
         return unit;
     }
 
-    /**
-     * Get all of the available general units' types.
-     * @return available units
-     * @internal
-     * @deprecated This API is ICU internal only.
-     */
-    public synchronized static Set<String> getAvailableTypes() {
-        return new HashSet<String>(cache.keySet());
-    }
+   
 
-    /**
-     * Get all of the available general units for a given type.
-     * @return available units
-     * @internal
-     * @deprecated This API is ICU internal only.
-     */
-    public synchronized static Collection<MeasureUnit> getAvailable(String type) {
-        Map<String, MeasureUnit> units = cache.get(type);
-        return units == null ? null : new ArrayList<MeasureUnit>(units.values());
-    }
-
-    /**
-     * Get all of the available general units.
-     * @return available units
-     * @internal
-     * @deprecated This API is ICU internal only.
-     */
-    public synchronized 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;
-    }
-
-    /**
-     * @internal
-     * @deprecated This API is ICU internal only.
-     */
-    @Override
-    public int hashCode() {
-        return code.hashCode() ^ type.hashCode();
-    }
-
-    /**
-     * @internal
-     * @deprecated This API is ICU internal only.
-     */
-    @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;
-        }
-    }
-    /**
-     * @internal
-     * @deprecated This API is ICU internal only.
-     */
-    public int compareTo(MeasureUnit other) {
-        int diff;
-        return this == other ? 0
-                : (diff = type.compareTo(other.type)) != 0 ? diff
-                        : code.compareTo(other.code);
-    }
-
-    /**
-     * @internal
-     * @deprecated This API is ICU internal only.
-     */
-    @Override
-    public String toString() {
-        return type + "-" + code;
-    }
-
-    /**
-     * @return the type for this unit
-     * @internal
-     * @deprecated This API is ICU internal only.
-     */
-    public String getType() {
-        return type;
-    }
-
-    /**
-     * @return the code for this unit.
-     * @internal
-     * @deprecated This API is ICU internal only.
-     */
-    public String getCode() {
-        return code;
-    }
 
     /** 
      * Useful constants. Not necessarily complete: see {@link #getAvailable()}.
-     * @internal
-     * @deprecated This API is ICU internal only.
+     * @draft ICU 53
+     * @provisional
      */
     public static final MeasureUnit
     /** Constant for unit of acceleration: g-force */
-    G_FORCE = MeasureUnit.getInstance("acceleration", "g-force"),
+    G_FORCE = MeasureUnit.internalGetInstance("acceleration", "g-force"),
     /** Constant for unit of angle: degree */
-    DEGREE = MeasureUnit.getInstance("angle", "degree"),
+    DEGREE = MeasureUnit.internalGetInstance("angle", "degree"),
     /** Constant for unit of angle: minute */
-    ARC_MINUTE = MeasureUnit.getInstance("angle", "arc-minute"),
+    ARC_MINUTE = MeasureUnit.internalGetInstance("angle", "arc-minute"),
     /** Constant for unit of angle: second */
-    ARC_SECOND = MeasureUnit.getInstance("angle", "arc-second"),
+    ARC_SECOND = MeasureUnit.internalGetInstance("angle", "arc-second"),
     /** Constant for unit of area: acre */
-    ACRE = MeasureUnit.getInstance("area", "acre"),
+    ACRE = MeasureUnit.internalGetInstance("area", "acre"),
     /** Constant for unit of area: hectare */
-    HECTARE = MeasureUnit.getInstance("area", "hectare"),
+    HECTARE = MeasureUnit.internalGetInstance("area", "hectare"),
     /** Constant for unit of area: square-foot */
-    SQUARE_FOOT = MeasureUnit.getInstance("area", "square-foot"),
+    SQUARE_FOOT = MeasureUnit.internalGetInstance("area", "square-foot"),
     /** Constant for unit of area: square-kilometer */
-    SQUARE_KILOMETER = MeasureUnit.getInstance("area", "square-kilometer"),
+    SQUARE_KILOMETER = MeasureUnit.internalGetInstance("area", "square-kilometer"),
     /** Constant for unit of area: square-meter */
-    SQUARE_METER = MeasureUnit.getInstance("area", "square-meter"),
+    SQUARE_METER = MeasureUnit.internalGetInstance("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"),
+    SQUARE_MILE = MeasureUnit.internalGetInstance("area", "square-mile"),
     /** 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"),
+    MILLISECOND = MeasureUnit.internalGetInstance("duration", "millisecond"),
     /** Constant for unit of length: centimeter */
-    CENTIMETER = MeasureUnit.getInstance("length", "centimeter"),
+    CENTIMETER = MeasureUnit.internalGetInstance("length", "centimeter"),
     /** Constant for unit of length: foot */
-    FOOT = MeasureUnit.getInstance("length", "foot"),
+    FOOT = MeasureUnit.internalGetInstance("length", "foot"),
     /** Constant for unit of length: inch */
-    INCH = MeasureUnit.getInstance("length", "inch"),
+    INCH = MeasureUnit.internalGetInstance("length", "inch"),
     /** Constant for unit of length: kilometer */
-    KILOMETER = MeasureUnit.getInstance("length", "kilometer"),
+    KILOMETER = MeasureUnit.internalGetInstance("length", "kilometer"),
     /** Constant for unit of length: light-year */
-    LIGHT_YEAR = MeasureUnit.getInstance("length", "light-year"),
+    LIGHT_YEAR = MeasureUnit.internalGetInstance("length", "light-year"),
     /** Constant for unit of length: meter */
-    METER = MeasureUnit.getInstance("length", "meter"),
+    METER = MeasureUnit.internalGetInstance("length", "meter"),
     /** Constant for unit of length: mile */
-    MILE = MeasureUnit.getInstance("length", "mile"),
+    MILE = MeasureUnit.internalGetInstance("length", "mile"),
     /** Constant for unit of length: millimeter */
-    MILLIMETER = MeasureUnit.getInstance("length", "millimeter"),
+    MILLIMETER = MeasureUnit.internalGetInstance("length", "millimeter"),
     /** Constant for unit of length: picometer */
-    PICOMETER = MeasureUnit.getInstance("length", "picometer"),
+    PICOMETER = MeasureUnit.internalGetInstance("length", "picometer"),
     /** Constant for unit of length: yard */
-    YARD = MeasureUnit.getInstance("length", "yard"),
+    YARD = MeasureUnit.internalGetInstance("length", "yard"),
     /** Constant for unit of mass: gram */
-    GRAM = MeasureUnit.getInstance("mass", "gram"),
+    GRAM = MeasureUnit.internalGetInstance("mass", "gram"),
     /** Constant for unit of mass: kilogram */
-    KILOGRAM = MeasureUnit.getInstance("mass", "kilogram"),
+    KILOGRAM = MeasureUnit.internalGetInstance("mass", "kilogram"),
     /** Constant for unit of mass: ounce */
-    OUNCE = MeasureUnit.getInstance("mass", "ounce"),
+    OUNCE = MeasureUnit.internalGetInstance("mass", "ounce"),
     /** Constant for unit of mass: pound */
-    POUND = MeasureUnit.getInstance("mass", "pound"),
+    POUND = MeasureUnit.internalGetInstance("mass", "pound"),
     /** Constant for unit of power: horsepower */
-    HORSEPOWER = MeasureUnit.getInstance("power", "horsepower"),
+    HORSEPOWER = MeasureUnit.internalGetInstance("power", "horsepower"),
     /** Constant for unit of power: kilowatt */
-    KILOWATT = MeasureUnit.getInstance("power", "kilowatt"),
+    KILOWATT = MeasureUnit.internalGetInstance("power", "kilowatt"),
     /** Constant for unit of power: watt */
-    WATT = MeasureUnit.getInstance("power", "watt"),
+    WATT = MeasureUnit.internalGetInstance("power", "watt"),
     /** Constant for unit of pressure: hectopascal */
-    HECTOPASCAL = MeasureUnit.getInstance("pressure", "hectopascal"),
+    HECTOPASCAL = MeasureUnit.internalGetInstance("pressure", "hectopascal"),
     /** Constant for unit of pressure: inch-hg */
-    INCH_HG = MeasureUnit.getInstance("pressure", "inch-hg"),
+    INCH_HG = MeasureUnit.internalGetInstance("pressure", "inch-hg"),
     /** Constant for unit of pressure: millibar */
-    MILLIBAR = MeasureUnit.getInstance("pressure", "millibar"),
+    MILLIBAR = MeasureUnit.internalGetInstance("pressure", "millibar"),
     /** Constant for unit of speed: kilometer-per-hour */
-    KILOMETER_PER_HOUR = MeasureUnit.getInstance("speed", "kilometer-per-hour"),
+    KILOMETER_PER_HOUR = MeasureUnit.internalGetInstance("speed", "kilometer-per-hour"),
     /** Constant for unit of speed: meter-per-second */
-    METER_PER_SECOND = MeasureUnit.getInstance("speed", "meter-per-second"),
+    METER_PER_SECOND = MeasureUnit.internalGetInstance("speed", "meter-per-second"),
     /** Constant for unit of speed: mile-per-hour */
-    MILE_PER_HOUR = MeasureUnit.getInstance("speed", "mile-per-hour"),
+    MILE_PER_HOUR = MeasureUnit.internalGetInstance("speed", "mile-per-hour"),
     /** Constant for unit of temperature: celsius */
-    CELSIUS = MeasureUnit.getInstance("temperature", "celsius"),
+    CELSIUS = MeasureUnit.internalGetInstance("temperature", "celsius"),
     /** Constant for unit of temperature: fahrenheit */
-    FAHRENHEIT = MeasureUnit.getInstance("temperature", "fahrenheit"),
+    FAHRENHEIT = MeasureUnit.internalGetInstance("temperature", "fahrenheit"),
     /** Constant for unit of volume: cubic-kilometer */
-    CUBIC_KILOMETER = MeasureUnit.getInstance("volume", "cubic-kilometer"),
+    CUBIC_KILOMETER = MeasureUnit.internalGetInstance("volume", "cubic-kilometer"),
     /** Constant for unit of volume: cubic-mile */
-    CUBIC_MILE = MeasureUnit.getInstance("volume", "cubic-mile"),
+    CUBIC_MILE = MeasureUnit.internalGetInstance("volume", "cubic-mile"),
     /** Constant for unit of volume: liter */
-    LITER = MeasureUnit.getInstance("volume", "liter");
+    LITER = MeasureUnit.internalGetInstance("volume", "liter");
+    
+    public static TimeUnit
+    /** Constant for unit of duration: year */
+    YEAR = (TimeUnit) MeasureUnit.internalGetInstance("duration", "year"),
+    /** Constant for unit of duration: month */
+    MONTH = (TimeUnit) MeasureUnit.internalGetInstance("duration", "month"),
+    /** Constant for unit of duration: week */
+    WEEK = (TimeUnit) MeasureUnit.internalGetInstance("duration", "week"),
+    /** Constant for unit of duration: day */
+    DAY = (TimeUnit) MeasureUnit.internalGetInstance("duration", "day"),
+    /** Constant for unit of duration: hour */
+    HOUR = (TimeUnit) MeasureUnit.internalGetInstance("duration", "hour"),
+    /** Constant for unit of duration: minute */
+    MINUTE = (TimeUnit) MeasureUnit.internalGetInstance("duration", "minute"),
+    /** Constant for unit of duration: second */
+    SECOND = (TimeUnit) MeasureUnit.internalGetInstance("duration", "second");
 
     /** Private **/
 
@@ -438,9 +449,7 @@ public class MeasureUnit implements Comparable<MeasureUnit>, Serializable {
         }
 
         private Object readResolve() throws ObjectStreamException {
-            return "currency".equals(type) 
-                    ? Currency.getInstance(code) 
-                            : MeasureUnit.getInstance(type, code);
+            return MeasureUnit.internalGetInstance(type, code);
         }
     }
 }
index 0e31490e531c1a30d551a2b92832a646bc65a394..036c7635982df067091164ddb332cb8e0c6ea764 100644 (file)
@@ -6,6 +6,9 @@
  */
 package com.ibm.icu.util;
 
+import java.io.InvalidObjectException;
+import java.io.ObjectStreamException;
+
 
 /**
  * Measurement unit for time units.
@@ -16,37 +19,15 @@ package com.ibm.icu.util;
  */
 public class TimeUnit extends MeasureUnit {
     private static final long serialVersionUID = -2839973855554750484L;
-
-    /** 
-     * Supports selected time duration units
-     */
-    private final int index;
-
-    // Total number of time units. Adjust as necessary.
-    static final int TIME_UNIT_COUNT = 7;
     
-    private static TimeUnit[] values = new TimeUnit[TIME_UNIT_COUNT];
-
-    /** 
-     * Constant value for supported time unit.
-     * @stable ICU 4.0
+    /**
+     * Here for serialization backward compatibility only.
      */
-    public static TimeUnit
-    SECOND = new TimeUnit("second", 6),
-    MINUTE = new TimeUnit("minute", 5),
-    HOUR = new TimeUnit("hour", 4),
-    DAY = new TimeUnit("day", 3),
-    WEEK = new TimeUnit("week", 2),
-    MONTH = new TimeUnit("month", 1),
-    YEAR = new TimeUnit("year", 0);
-    
+    private final int index;
 
-    // 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) {
-        super("duration", name);
-        this.index = idx;
-        values[idx] = this; // store in values array
+    TimeUnit(String type, String code) {
+        super(type, code);
+        index = 0;
     }
 
     /**
@@ -54,12 +35,40 @@ public class TimeUnit extends MeasureUnit {
      * @stable ICU 4.0
      */
     public static TimeUnit[] values() {
-        return values.clone();
+        return new TimeUnit[] {
+                (TimeUnit) SECOND,
+                (TimeUnit) MINUTE,
+                (TimeUnit) HOUR,
+                (TimeUnit) DAY,
+                (TimeUnit) WEEK,
+                (TimeUnit) MONTH,
+                (TimeUnit) YEAR};
     }
-
-    // Returns the index for this TimeUnit. Something between 0 inclusive and
-    // number of time units exclusive. Smaller time units have larger indexes.
-    int getIndex() {
-        return index;
+    
+    private Object writeReplace() throws ObjectStreamException {
+        return new MeasureUnitProxy(type, code);
+    }
+    
+    // For backward compatibility only
+    private Object readResolve() throws ObjectStreamException {
+        // The old index field used to uniquely identify the time unit.
+        switch (index) {
+        case 6:
+            return SECOND;
+        case 5:
+            return MINUTE;
+        case 4:
+            return HOUR;
+        case 3:
+            return DAY;
+        case 2:
+            return WEEK;
+        case 1:
+            return MONTH;
+        case 0:
+            return YEAR;
+        default:
+            throw new InvalidObjectException("Bad index: " + index);
+        }
     }
 }
index 017ebb48c832d651ace4e508d688b2d9c2304eaf..9c23c50867574ee9cef6ed5b6d8206a9a9b41c40 100644 (file)
@@ -14,7 +14,9 @@ import java.io.ObjectOutputStream;
 import java.io.Serializable;
 import java.text.FieldPosition;
 import java.text.ParsePosition;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.TreeSet;
@@ -23,10 +25,10 @@ import com.ibm.icu.dev.test.TestFmwk;
 import com.ibm.icu.dev.test.serializable.SerializableTest;
 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.MeasureFormat.FormatWidth;
 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.TimeUnit;
@@ -38,27 +40,6 @@ import com.ibm.icu.util.ULocale;
  */
 public class MeasureUnitTest extends TestFmwk {
     
-    private static final TimeUnitAmount[] _19m = {new TimeUnitAmount(19.0, TimeUnit.MINUTE)};
-    private static final TimeUnitAmount[] _1h_23_5s = {
-            new TimeUnitAmount(1.0, TimeUnit.HOUR),
-            new TimeUnitAmount(23.5, TimeUnit.SECOND)};
-    private static final TimeUnitAmount[] _1h_23_5m = {
-            new TimeUnitAmount(1.0, TimeUnit.HOUR),
-            new TimeUnitAmount(23.5, TimeUnit.MINUTE)};
-    private static final TimeUnitAmount[] _1h_0m_23s = {
-            new TimeUnitAmount(1.0, TimeUnit.HOUR),
-            new TimeUnitAmount(0.0, TimeUnit.MINUTE),
-            new TimeUnitAmount(23.0, TimeUnit.SECOND)};
-    private static final TimeUnitAmount[] _2y_5M_3w_4d = {
-            new TimeUnitAmount(2.0, TimeUnit.YEAR),
-            new TimeUnitAmount(5.0, TimeUnit.MONTH),
-            new TimeUnitAmount(3.0, TimeUnit.WEEK),
-            new TimeUnitAmount(4.0, TimeUnit.DAY)};
-    private static final TimeUnitAmount[] _1m_59_9996s = {
-            new TimeUnitAmount(1.0, TimeUnit.MINUTE),
-            new TimeUnitAmount(59.9996, TimeUnit.SECOND)};
-    
-    
     /**
      * @author markdavis
      *
@@ -69,6 +50,26 @@ public class MeasureUnitTest extends TestFmwk {
     }
     
     public void TestFormatPeriodEn() {
+        TimeUnitAmount[] _19m = {new TimeUnitAmount(19.0, TimeUnit.MINUTE)};
+        TimeUnitAmount[] _1h_23_5s = {
+                new TimeUnitAmount(1.0, TimeUnit.HOUR),
+                new TimeUnitAmount(23.5, TimeUnit.SECOND)};
+        TimeUnitAmount[] _1h_23_5m = {
+                new TimeUnitAmount(1.0, TimeUnit.HOUR),
+                new TimeUnitAmount(23.5, TimeUnit.MINUTE)};
+        TimeUnitAmount[] _1h_0m_23s = {
+                new TimeUnitAmount(1.0, TimeUnit.HOUR),
+                new TimeUnitAmount(0.0, TimeUnit.MINUTE),
+                new TimeUnitAmount(23.0, TimeUnit.SECOND)};
+        TimeUnitAmount[] _2y_5M_3w_4d = {
+                new TimeUnitAmount(2.0, TimeUnit.YEAR),
+                new TimeUnitAmount(5.0, TimeUnit.MONTH),
+                new TimeUnitAmount(3.0, TimeUnit.WEEK),
+                new TimeUnitAmount(4.0, TimeUnit.DAY)};
+        TimeUnitAmount[] _1m_59_9996s = {
+                new TimeUnitAmount(1.0, TimeUnit.MINUTE),
+                new TimeUnitAmount(59.9996, TimeUnit.SECOND)};
+        
         Object[][] fullData = {
                 {_1m_59_9996s, "1 minute, 59.9996 seconds"},
                 {_19m, "19 minutes"},
@@ -97,23 +98,23 @@ public class MeasureUnitTest extends TestFmwk {
                 {_2y_5M_3w_4d, "2 yrs, 5 mths, 3 wks, 4 days"},
                 {_0h_0m_17s, "0:00:17"},
                 {_6h_56_92m, "6:56.92"}};
-       */
+        */
         
         NumberFormat nf = NumberFormat.getNumberInstance(ULocale.ENGLISH);
         nf.setMaximumFractionDigits(4);
-        GeneralMeasureFormat gmf = GeneralMeasureFormat.getInstance(ULocale.ENGLISH, FormatWidth.WIDE, nf);
-        verifyFormatPeriod("en FULL", gmf, fullData);
-        gmf = GeneralMeasureFormat.getInstance(ULocale.ENGLISH, FormatWidth.SHORT, nf);
-        verifyFormatPeriod("en SHORT", gmf, abbrevData);
-       
-       
+        MeasureFormat mf = MeasureFormat.getInstance(ULocale.ENGLISH, FormatWidth.WIDE, nf);
+        verifyFormatPeriod("en FULL", mf, fullData);
+        mf = MeasureFormat.getInstance(ULocale.ENGLISH, FormatWidth.SHORT, nf);
+        verifyFormatPeriod("en SHORT", mf, abbrevData);
     }
     
-    private void verifyFormatPeriod(String desc, GeneralMeasureFormat gmf, Object[][] testData) {
+
+    
+    private void verifyFormatPeriod(String desc, MeasureFormat mf, Object[][] testData) {
         StringBuilder builder = new StringBuilder();
         boolean failure = false;
         for (Object[] testCase : testData) {
-            String actual = gmf.format((Measure[]) testCase[0]);
+            String actual = mf.format((Measure[]) testCase[0]);
             if (!testCase[1].equals(actual)) {
                 builder.append(String.format("%s: Expected: '%s', got: '%s'\n", desc, testCase[1], actual));
                 failure = true;
@@ -128,138 +129,175 @@ public class MeasureUnitTest extends TestFmwk {
         String lastType = null;
         for (MeasureUnit expected : MeasureUnit.getAvailable()) {
             String type = expected.getType();
-            String code = expected.getCode();
+            String code = expected.getSubtype();
             if (!type.equals(lastType)) {
                 logln(type);
                 lastType = type;
             }
-            MeasureUnit actual = MeasureUnit.getInstance(type, code);
+            MeasureUnit actual = MeasureUnit.internalGetInstance(type, code);
             assertSame("Identity check", expected, actual);
         }
     }
-    
-    public void testTimePeriods() {
-        
-    }
 
     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);
-            }
-
+        ULocale russia = new ULocale("ru");
+        Object[][] data = new Object[][] {
+                {ULocale.ENGLISH, FormatWidth.WIDE, "2 miles, 1 foot, 2.3 inches"},
+                {ULocale.ENGLISH, FormatWidth.SHORT, "2 mi, 1 ft, 2.3 in"},
+                {ULocale.ENGLISH, FormatWidth.NARROW, "2mi, 1′, 2.3″"},
+                {russia, FormatWidth.WIDE, "2 мили, 1 фут и 2,3 дюйма"},
+                {russia, FormatWidth.SHORT, "2 мили 1 фут 2,3 дюйма"},
+                {russia, FormatWidth.NARROW, "2 мили 1 фут 2,3 дюйма"},
+        };
+        for (Object[] row : data) {
+            MeasureFormat mf = MeasureFormat.getInstance(
+                    (ULocale) row[0], (FormatWidth) row[1]);
+            assertEquals(
+                    "testMultiples",
+                    row[2],
+                    mf.formatMeasures(
+                            new Measure(2, MeasureUnit.MILE), 
+                            new Measure(1, MeasureUnit.FOOT), 
+                            new Measure(2.3, MeasureUnit.INCH)));
         }
     }
 
     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
-                + "\t«" + 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);
-        }
+        MeasureFormat mf = MeasureFormat.getInstance(ULocale.ENGLISH, FormatWidth.SHORT);
+        assertEquals(
+                "testGram",
+                "1 g",
+                mf.format(new Measure(1, MeasureUnit.GRAM)));
+        assertEquals(
+                "testGram",
+                "1 G",
+                mf.format(new Measure(1, MeasureUnit.G_FORCE)));
     }
 
     public void testExamples() {
-        GeneralMeasureFormat fmtFr = GeneralMeasureFormat.getInstance(ULocale.FRENCH, FormatWidth.SHORT);
+        MeasureFormat fmtFr = MeasureFormat.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);
+        MeasureFormat fmtFrFull = MeasureFormat.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),
+            assertEquals("", "70 pieds, 5,3 pouces", fmtFrFull.formatMeasures(new Measure(70, MeasureUnit.FOOT),
                     new Measure(5.3, MeasureUnit.INCH)));
-            assertEquals("", "1 pied, 1 pouce", fmtFrFull.format(new Measure(1, MeasureUnit.FOOT),
+            assertEquals("", "1 pied, 1 pouce", fmtFrFull.formatMeasures(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),
+        MeasureFormat fmtEn = MeasureFormat.getInstance(ULocale.ENGLISH, FormatWidth.WIDE);
+        assertEquals("", "1 inch, 2 feet", fmtEn.formatMeasures(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);
+    }
+    
+    public void testFieldPosition() {
+        MeasureFormat fmt = MeasureFormat.getInstance(
+                ULocale.ENGLISH, FormatWidth.SHORT);
+        FieldPosition pos = new FieldPosition(NumberFormat.Field.DECIMAL_SEPARATOR);
+        fmt.format(new Measure(43.5, MeasureUnit.FOOT), new StringBuffer(), pos);
+        assertEquals("beginIndex", 2, pos.getBeginIndex());
+        assertEquals("endIndex", 3, pos.getEndIndex());
+        
+        pos = new FieldPosition(NumberFormat.Field.DECIMAL_SEPARATOR);
+        fmt.format(new Measure(43, MeasureUnit.FOOT), new StringBuffer(), pos);
+        assertEquals("beginIndex", 0, pos.getBeginIndex());
+        assertEquals("endIndex", 0, pos.getEndIndex());
+    }
+    
+    public void testFieldPositionMultiple() {
+        MeasureFormat fmt = MeasureFormat.getInstance(
+                ULocale.ENGLISH, FormatWidth.SHORT);
+        FieldPosition pos = new FieldPosition(NumberFormat.Field.INTEGER);
+        String result = fmt.formatMeasures(
+                new StringBuilder(),
+                pos,
+                new Measure(354, MeasureUnit.METER),
+                new Measure(23, MeasureUnit.CENTIMETER)).toString();
+        assertEquals("result", "354 m, 23 cm", result);
+        
+        // According to javadocs for {@link Format#format} FieldPosition is set to
+        // beginning and end of first such field encountered instead of the last
+        // such field encountered.
+        assertEquals("beginIndex", 0, pos.getBeginIndex());
+        assertEquals("endIndex", 3, pos.getEndIndex());
+        
+        pos = new FieldPosition(NumberFormat.Field.DECIMAL_SEPARATOR);
+        result = fmt.formatMeasures(
+                new StringBuilder(),
+                pos,
+                new Measure(354, MeasureUnit.METER),
+                new Measure(23, MeasureUnit.CENTIMETER),
+                new Measure(5.4, MeasureUnit.MILLIMETER)).toString();
+        assertEquals("result", "354 m, 23 cm, 5.4 mm", result);
+        assertEquals("beginIndex", 15, pos.getBeginIndex());
+        assertEquals("endIndex", 16, pos.getEndIndex());
+        
+        result = fmt.formatMeasures(
+                new StringBuilder(),
+                pos,
+                new Measure(3, MeasureUnit.METER),
+                new Measure(23, MeasureUnit.CENTIMETER),
+                new Measure(5.4, MeasureUnit.MILLIMETER)).toString();
+        assertEquals("result", "3 m, 23 cm, 5.4 mm", result);
+        assertEquals("beginIndex", 13, pos.getBeginIndex());
+        assertEquals("endIndex", 14, pos.getEndIndex());
+        
+        pos = new FieldPosition(NumberFormat.Field.DECIMAL_SEPARATOR);
+        result = fmt.formatMeasures(
+                new StringBuilder(),
+                pos,
+                new Measure(3, MeasureUnit.METER),
+                new Measure(23, MeasureUnit.CENTIMETER),
+                new Measure(5, MeasureUnit.MILLIMETER)).toString();
+        assertEquals("result", "3 m, 23 cm, 5 mm", result);
+        assertEquals("beginIndex", 0, pos.getBeginIndex());
+        assertEquals("endIndex", 0, pos.getEndIndex());
+        
+    }
+    
+    public void testOldFormatWithList() {
+        List<Measure> measures = new ArrayList<Measure>(2);
+        measures.add(new Measure(5, MeasureUnit.ACRE));
+        measures.add(new Measure(3000, MeasureUnit.SQUARE_FOOT));
+        MeasureFormat fmt = MeasureFormat.getInstance(
+                ULocale.ENGLISH, FormatWidth.WIDE);
+        assertEquals("", "5 acres, 3,000 square feet", fmt.format(measures));
+        assertEquals("", "5 acres", fmt.format(measures.subList(0, 1)));
+        List<String> badList = new ArrayList<String>();
+        badList.add("be");
+        badList.add("you");
+        try {
+            fmt.format(badList);
+            fail("Expected IllegalArgumentException.");
+        } catch (IllegalArgumentException expected) {
+           // Expected 
+        }
+    }
+    
+    public void testOldFormatWithArray() {
+        Measure[] measures = new Measure[] {
+                new Measure(5, MeasureUnit.ACRE),
+                new Measure(3000, MeasureUnit.SQUARE_FOOT),  
+        };
+        MeasureFormat fmt = MeasureFormat.getInstance(
+                ULocale.ENGLISH, FormatWidth.WIDE);
+        assertEquals("", "5 acres, 3,000 square feet", fmt.format(measures));
+    }
+    
+    public void testOldFormatBadArg() {
+        MeasureFormat fmt = MeasureFormat.getInstance(
+                ULocale.ENGLISH, FormatWidth.WIDE);
+        try {
+            fmt.format("be");
+            fail("Expected IllegalArgumentExceptino.");
+        } catch (IllegalArgumentException e) {
+            // Expected
         }
-        // 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() {
@@ -268,7 +306,7 @@ public class MeasureUnitTest extends TestFmwk {
         boolean first = true;
         for (String type : new TreeSet<String>(MeasureUnit.getAvailableTypes())) {
             for (MeasureUnit unit : MeasureUnit.getAvailable(type)) {
-                String code = unit.getCode();
+                String code = unit.getSubtype();
                 String name = code.toUpperCase(Locale.ENGLISH).replace("-", "_");
 
                 if (type.equals("angle")) {
@@ -303,8 +341,10 @@ public class MeasureUnitTest extends TestFmwk {
     
     public void TestSerial() {
         checkStreamingEquality(MeasureUnit.CELSIUS);
-        checkStreamingEquality(GeneralMeasureFormat.getInstance(ULocale.FRANCE, FormatWidth.NARROW));
+        checkStreamingEquality(MeasureFormat.getInstance(ULocale.FRANCE, FormatWidth.NARROW));
         checkStreamingEquality(Currency.getInstance("EUR"));
+        checkStreamingEquality(MeasureFormat.getInstance(ULocale.GERMAN, FormatWidth.SHORT));
+        checkStreamingEquality(MeasureFormat.getCurrencyFormat(ULocale.ITALIAN));
     }
     
     public <T extends Serializable> void checkStreamingEquality(T item) {
@@ -320,6 +360,7 @@ public class MeasureUnitTest extends TestFmwk {
           Object obj = objectInputStream.readObject();
           assertEquals("Streamed Object equals ", item, obj);
         } catch (IOException e) {
+          e.printStackTrace();
           assertNull("Test Serialization " + item.getClass(), e);
         } catch (ClassNotFoundException e) {
           assertNull("Test Serialization " + item.getClass(), e);
@@ -353,35 +394,35 @@ public class MeasureUnitTest extends TestFmwk {
             };
             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());
+            return a1.getType().equals(b1.getType())
+                    && a1.getSubtype().equals(b1.getSubtype());
         }
     }
-    
-    public static class GeneralMeasureFormatHandler  implements SerializableTest.Handler
+   
+    public static class MeasureFormatHandler  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
-                            )),
+            MeasureFormat items[] = {
+                    MeasureFormat.getInstance(ULocale.FRANCE, FormatWidth.SHORT),
+                    MeasureFormat.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())
+            MeasureFormat a1 = (MeasureFormat) a;
+            MeasureFormat b1 = (MeasureFormat) b;
+            return a1.getLocale().equals(b1.getLocale())
+                    && a1.getWidth().equals(b1.getWidth())
+                    && a1.getNumberFormat().equals(b1.getNumberFormat())
                     ;
         }
     }
index 28e04f98bef314b837e792f8c6b6dc07160bc0b8..3de8d5c4582a3ed781cc7a7604c93abb942d5e17 100644 (file)
@@ -132,6 +132,9 @@ public class CompatibilityTest extends TestFmwk
 
         {"ICU_50.1", "com.ibm.icu.text.PluralRules.dat"},
         {"ICU_51.1", "com.ibm.icu.text.PluralRules.dat"},
+        
+        // GeneralMeasureFormat was in technical preview, but is going away after ICU 52.1.
+        {"ICU_52.1", "com.ibm.icu.text.GeneralMeasureFormat.dat"},
 
         // RuleBasedNumberFormat
         {"ICU_3.6",     "com.ibm.icu.text.RuleBasedNumberFormat.dat"},
index 6077db33915ac78dde1263a7fe08833abefa4aa0..8f89117d2ebcc06c7920d94251e09eef81252854 100644 (file)
@@ -718,7 +718,7 @@ public class SerializableTest extends TestFmwk.TestGroup
         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());
+        map.put("com.ibm.icu.text.MeasureFormat", new MeasureUnitTest.MeasureFormatHandler());
     }
     
     public SerializableTest()