]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-52_1/main/classes/core/src/com/ibm/icu/text/GeneralMeasureFormat.java
Upgrade ICU4J.
[Dictionary.git] / jars / icu4j-52_1 / main / classes / core / src / com / ibm / icu / text / GeneralMeasureFormat.java
1 /*
2  *******************************************************************************
3  * Copyright (C) 2013, Google Inc, International Business Machines Corporation and         *
4  * others. All Rights Reserved.                                                *
5  *******************************************************************************
6  */
7 package com.ibm.icu.text;
8
9 import java.io.Externalizable;
10 import java.io.IOException;
11 import java.io.ObjectInput;
12 import java.io.ObjectOutput;
13 import java.io.ObjectStreamException;
14 import java.text.FieldPosition;
15 import java.text.ParsePosition;
16 import java.util.ArrayList;
17 import java.util.BitSet;
18 import java.util.Collection;
19 import java.util.Comparator;
20 import java.util.EnumMap;
21 import java.util.HashMap;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.Map.Entry;
25 import java.util.MissingResourceException;
26 import java.util.Set;
27 import java.util.TreeMap;
28
29 import com.ibm.icu.impl.ICUResourceBundle;
30 import com.ibm.icu.util.FormatWidth;
31 import com.ibm.icu.util.Measure;
32 import com.ibm.icu.util.MeasureUnit;
33 import com.ibm.icu.util.ULocale;
34 import com.ibm.icu.util.ULocale.Category;
35 import com.ibm.icu.util.UResourceBundle;
36
37 /**
38  * Mutable class for formatting GeneralMeasures, or sequences of them.
39  * @author markdavis
40  * @internal
41  * @deprecated This API is ICU internal only.
42  */
43 public class GeneralMeasureFormat extends MeasureFormat {
44
45     // Cache the data for units so we don't have to look it up each time.
46     // For each format, we'll store a pointer into the EnumMap for quick access.
47     // TODO use the data to allow parsing.
48     static final transient Map<ULocale,ParseData> localeToParseData = new HashMap<ULocale,ParseData>();
49     static final transient Map<ULocale,Map<MeasureUnit, EnumMap<FormatWidth, Map<String, PatternData>>>> localeToUnitToStyleToCountToFormat 
50     = new HashMap<ULocale,Map<MeasureUnit, EnumMap<FormatWidth, Map<String, PatternData>>>>();
51     static final transient Index<MeasureUnit> index = new Index<MeasureUnit>();
52
53     static final class PatternData {
54         final String prefix;
55         final String suffix;
56         public PatternData(String pattern) {
57             int pos = pattern.indexOf("{0}");
58             if (pos < 0) {
59                 prefix = pattern;
60                 suffix = null;
61             } else {
62                 prefix = pattern.substring(0,pos);
63                 suffix = pattern.substring(pos+3);
64             }
65         }
66         public String toString() {
67             return prefix + "; " + suffix;
68         }
69
70     }
71     private final ULocale locale;
72     private final FormatWidth length;
73     private final NumberFormat numberFormat;
74
75     private final transient PluralRules rules;
76     private final transient Map<MeasureUnit, EnumMap<FormatWidth, Map<String, PatternData>>> unitToStyleToCountToFormat; // invariant once built
77     private transient ParseData parseData; // set as needed
78
79
80     private static final long serialVersionUID = 7922671801770278517L;
81
82     /**
83      * @internal
84      * @deprecated This API is ICU internal only.
85      */
86     protected GeneralMeasureFormat(ULocale locale, FormatWidth style, 
87             Map<MeasureUnit, EnumMap<FormatWidth, Map<String, PatternData>>> unitToStyleToCountToFormat,
88             NumberFormat numberFormat) {
89         this.locale = locale;
90         this.length = style;
91         this.unitToStyleToCountToFormat = unitToStyleToCountToFormat;
92         rules = PluralRules.forLocale(locale);
93         this.numberFormat = numberFormat;
94     }
95
96
97     /**
98      * Create a format from the locale and length
99      * @param locale   locale of this time unit formatter.
100      * @param length the desired length
101      * @internal
102      * @deprecated This API is ICU internal only.
103      */
104     public static GeneralMeasureFormat getInstance(ULocale locale, FormatWidth length) {
105         return getInstance(locale, length, NumberFormat.getInstance(locale));
106     }
107
108     /**
109      * Create a format from the locale and length
110      * @param locale   locale of this time unit formatter.
111      * @param length the desired length
112      * @internal
113      * @deprecated This API is ICU internal only.
114      */
115     public static GeneralMeasureFormat getInstance(ULocale locale, FormatWidth length,
116             NumberFormat decimalFormat) {
117         synchronized (localeToUnitToStyleToCountToFormat) {
118             Map<MeasureUnit, EnumMap<FormatWidth, Map<String, PatternData>>> unitToStyleToCountToFormat 
119             = localeToUnitToStyleToCountToFormat.get(locale);
120             if (unitToStyleToCountToFormat == null) {
121                 unitToStyleToCountToFormat = cacheLocaleData(locale);
122             }
123             //            System.out.println(styleToCountToFormat);            
124             return new GeneralMeasureFormat(locale, length, unitToStyleToCountToFormat, decimalFormat);
125         }
126     }
127
128     /**
129      * Return a formatter for CurrencyAmount objects in the given
130      * locale.
131      * @param locale desired locale
132      * @internal
133      * @deprecated This API is ICU internal only.
134      */
135     public static MeasureFormat getCurrencyFormat(ULocale locale) {
136         return new CurrencyFormat(locale);
137     }
138
139     /**
140      * Return a formatter for CurrencyAmount objects in the default
141      * <code>FORMAT</code> locale.
142      * @return a formatter object
143      * @internal
144      * @deprecated This API is ICU internal only.
145      */
146     public static MeasureFormat getCurrencyFormat() {
147         return getCurrencyFormat(ULocale.getDefault(Category.FORMAT));
148     }
149
150     /**
151      * @return the locale of the format.
152      * @internal
153      * @deprecated This API is ICU internal only.
154      */
155     public ULocale getLocale() {
156         return locale;
157     }
158
159     /**
160      * @return the desired length for the format
161      * @internal
162      * @deprecated This API is ICU internal only.
163      */
164     public FormatWidth getLength() {
165         return length;
166     }
167
168     private static Map<MeasureUnit, EnumMap<FormatWidth, Map<String, PatternData>>> cacheLocaleData(ULocale locale) {
169         PluralRules rules = PluralRules.forLocale(locale);
170         Set<String> keywords = rules.getKeywords();
171         Map<MeasureUnit, EnumMap<FormatWidth, Map<String, PatternData>>> unitToStyleToCountToFormat;
172         localeToUnitToStyleToCountToFormat.put(locale, unitToStyleToCountToFormat 
173                 = new HashMap<MeasureUnit, EnumMap<FormatWidth, Map<String, PatternData>>>());
174         for (MeasureUnit unit : MeasureUnit.getAvailable()) {
175             EnumMap<FormatWidth, Map<String, PatternData>> styleToCountToFormat = unitToStyleToCountToFormat.get(unit);
176             if (styleToCountToFormat == null) {
177                 unitToStyleToCountToFormat.put(unit, styleToCountToFormat = new EnumMap<FormatWidth, Map<String, PatternData>>(FormatWidth.class));
178             }
179             ICUResourceBundle resource = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME, locale);
180             for (FormatWidth styleItem : FormatWidth.values()) {
181                 try {
182                     ICUResourceBundle unitTypeRes = resource.getWithFallback(styleItem.resourceKey);
183                     ICUResourceBundle unitsRes = unitTypeRes.getWithFallback(unit.getType());
184                     ICUResourceBundle oneUnitRes = unitsRes.getWithFallback(unit.getCode());
185                     Map<String, PatternData> countToFormat = styleToCountToFormat.get(styleItem);
186                     if (countToFormat == null) {
187                         styleToCountToFormat.put(styleItem, countToFormat = new HashMap<String, PatternData>());
188                     }
189                     for (String keyword : keywords) {
190                         UResourceBundle countBundle;
191                         try {
192                             countBundle = oneUnitRes.get(keyword);
193                         } catch (MissingResourceException e) {
194                             continue;
195                         }
196                         String pattern = countBundle.getString();
197                         //                        System.out.println(styleItem.resourceKey + "/" 
198                         //                                + unit.getType() + "/" 
199                         //                                + unit.getCode() + "/" 
200                         //                                + keyword + "=" + pattern);
201                         PatternData format = new PatternData(pattern);
202                         countToFormat.put(keyword, format);
203                         //                        System.out.println(styleToCountToFormat);
204                     }
205                     // fill in 'other' for any missing values
206                     PatternData other = countToFormat.get("other");
207                     for (String keyword : keywords) {
208                         if (!countToFormat.containsKey(keyword)) {
209                             countToFormat.put(keyword, other);
210                         }
211                     }
212                 } catch (MissingResourceException e) {
213                     continue;
214                 }
215             }
216             // now fill in the holes
217             fillin:
218                 if (styleToCountToFormat.size() != FormatWidth.values().length) {
219                     Map<String, PatternData> fallback = styleToCountToFormat.get(FormatWidth.SHORT);
220                     if (fallback == null) {
221                         fallback = styleToCountToFormat.get(FormatWidth.WIDE);
222                     }
223                     if (fallback == null) {
224                         break fillin; // TODO use root
225                     }
226                     for (FormatWidth styleItem : FormatWidth.values()) {
227                         Map<String, PatternData> countToFormat = styleToCountToFormat.get(styleItem);
228                         if (countToFormat == null) {
229                             styleToCountToFormat.put(styleItem, countToFormat = new HashMap<String, PatternData>());
230                             for (Entry<String, PatternData> entry : fallback.entrySet()) {
231                                 countToFormat.put(entry.getKey(), entry.getValue());
232                             }
233                         }
234                     }
235                 }
236         }
237         return unitToStyleToCountToFormat;
238     }
239
240     /**
241      * @internal
242      * @deprecated This API is ICU internal only.
243      */
244     @SuppressWarnings("unchecked")
245     @Override
246     public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
247         if (obj instanceof Collection) {
248             Collection<Measure> coll = (Collection<Measure>) obj;
249             return format(toAppendTo, pos, coll.toArray(new Measure[coll.size()]));
250         } else if (obj instanceof Measure[]) {
251             return format(toAppendTo, pos, (Measure[]) obj);
252         } else {
253             return format((Measure) obj, toAppendTo, pos);
254         }
255     }
256
257     /**
258      * Format a general measure (type-safe).
259      * @param measure the measure to format
260      * @param toAppendTo as in {@link #format(Object, StringBuffer, FieldPosition)}
261      * @param pos as in {@link #format(Object, StringBuffer, FieldPosition)}
262      * @return passed-in buffer with appended text.
263      * @internal
264      * @deprecated This API is ICU internal only.
265      */
266     public StringBuffer format(Measure measure, StringBuffer toAppendTo, FieldPosition pos) {
267         Number n = measure.getNumber();
268         MeasureUnit unit = measure.getUnit();        
269         UFieldPosition fpos = new UFieldPosition(pos.getFieldAttribute(), pos.getField());
270         StringBuffer formattedNumber = numberFormat.format(n, new StringBuffer(), fpos);
271         String keyword = rules.select(new PluralRules.FixedDecimal(n.doubleValue(), fpos.getCountVisibleFractionDigits(), fpos.getFractionDigits()));
272
273         Map<FormatWidth, Map<String, PatternData>> styleToCountToFormat = unitToStyleToCountToFormat.get(unit);
274         Map<String, PatternData> countToFormat = styleToCountToFormat.get(length);
275         PatternData messagePatternData = countToFormat.get(keyword);
276
277         toAppendTo.append(messagePatternData.prefix);
278         if (messagePatternData.suffix != null) { // there is a number (may not happen with, say, Arabic dual)
279             // Fix field position
280             pos.setBeginIndex(fpos.getBeginIndex() + messagePatternData.prefix.length());
281             pos.setEndIndex(fpos.getEndIndex() + messagePatternData.prefix.length());
282             toAppendTo.append(formattedNumber);
283             toAppendTo.append(messagePatternData.suffix);
284         }
285         return toAppendTo;
286     }
287
288
289     /**
290      * Format a sequence of measures.
291      * @param toAppendto as in {@link #format(Object, StringBuffer, FieldPosition)}
292      * @param pos as in {@link #format(Object, StringBuffer, FieldPosition)}
293      * @param measures a sequence of one or more measures.
294      * @return passed-in buffer with appended text.
295      * @internal
296      * @deprecated This API is ICU internal only.
297      */
298     public StringBuffer format(StringBuffer toAppendto, FieldPosition pos, Measure... measures) {
299         StringBuffer[] results = new StringBuffer[measures.length];
300         for (int i = 0; i < measures.length; ++i) {
301             results[i] = format(measures[i], new StringBuffer(), pos);
302         }
303         ListFormatter listFormatter = ListFormatter.getInstance(locale, 
304                 length == FormatWidth.WIDE ? ListFormatter.Style.DURATION : ListFormatter.Style.DURATION_SHORT);
305         return toAppendto.append(listFormatter.format((Object[]) results));
306     }
307
308     /**
309      * Format a sequence of measures.
310      * @param measures a sequence of one or more measures.
311      * @return passed-in buffer with appended text.
312      * @internal
313      * @deprecated This API is ICU internal only.
314      */
315     public String format(Measure... measures) {
316         StringBuffer result = format(new StringBuffer(), new FieldPosition(0), measures);
317         return result.toString();
318     }
319
320     static final class ParseData {
321         transient Map<String,BitSet> prefixMap;
322         transient Map<String,BitSet> suffixMap;
323         transient BitSet nullSuffix;
324
325         ParseData(ULocale locale, Map<MeasureUnit, EnumMap<FormatWidth, Map<String, PatternData>>> unitToStyleToCountToFormat) {
326             prefixMap = new TreeMap<String,BitSet>(LONGEST_FIRST);
327             suffixMap = new TreeMap<String,BitSet>(LONGEST_FIRST);
328             nullSuffix = new BitSet();
329             for (Entry<MeasureUnit, EnumMap<FormatWidth, Map<String, PatternData>>> entry3 : unitToStyleToCountToFormat.entrySet()) {
330                 MeasureUnit unit = entry3.getKey();
331                 int unitIndex = index.addItem(unit);
332                 for (Entry<FormatWidth, Map<String, PatternData>> entry : entry3.getValue().entrySet()) {
333                     //Style style = entry.getKey();
334                     for (Entry<String, PatternData> entry2 : entry.getValue().entrySet()) {
335                         //String keyword = entry2.getKey();
336                         PatternData data = entry2.getValue();
337                         setBits(prefixMap, data.prefix, unitIndex);
338                         if (data.suffix == null) {
339                             nullSuffix.set(unitIndex);
340                         } else {
341                             setBits(suffixMap, data.suffix, unitIndex);
342                         }
343                     }
344                 }
345             }
346         }
347         private void setBits(Map<String, BitSet> map, String string, int unitIndex) {
348             BitSet bs = map.get(string);
349             if (bs == null) {
350                 map.put(string, bs = new BitSet());
351             }
352             bs.set(unitIndex);
353         }
354         public static synchronized ParseData of(ULocale locale,
355                 Map<MeasureUnit, EnumMap<FormatWidth, Map<String, PatternData>>> unitToStyleToCountToFormat) {
356             ParseData result = localeToParseData.get(locale);
357             if (result == null) {
358                 localeToParseData.put(locale, result = new ParseData(locale, unitToStyleToCountToFormat));
359                 //                System.out.println("Prefix:\t" + result.prefixMap.size());
360                 //                System.out.println("Suffix:\t" + result.suffixMap.size());
361             }
362             return result;
363         }
364
365         private Measure parse(NumberFormat numberFormat, String toParse, ParsePosition parsePosition) {
366             // TODO optimize this as necessary
367             // In particular, if we've already matched a suffix and number, store that.
368             // If the same suffix turns up we can jump
369             int startIndex = parsePosition.getIndex();
370             Number bestNumber = null;
371             int bestUnit = -1;
372             int longestMatch = -1;
373             int furthestError = -1;
374             for (Entry<String, BitSet> prefixEntry : prefixMap.entrySet()) {
375                 String prefix = prefixEntry.getKey();
376                 BitSet prefixSet = prefixEntry.getValue();
377                 for (Entry<String, BitSet> suffixEntry : suffixMap.entrySet()) {
378                     String suffix = suffixEntry.getKey();
379                     BitSet suffixSet = suffixEntry.getValue();
380                     parsePosition.setIndex(startIndex);
381                     if (looseMatches(prefix, toParse, parsePosition)) {
382                         //                    if (nullSuffix.intersects(prefixSet))
383                         ////                        // can only happen with singular rule
384                         ////                        if (longestMatch < parsePosition.getIndex()) {
385                         ////                            longestMatch = parsePosition.getIndex();
386                         ////                            Collection<Double> samples = rules.getSamples(keyword);
387                         ////                            bestNumber = samples.iterator().next();
388                         ////                            bestUnit = unit;
389                         ////                        }
390                         //                    }
391                         Number number = numberFormat.parse(toParse, parsePosition);
392                         if (parsePosition.getErrorIndex() >= 0) {
393                             if (furthestError < parsePosition.getErrorIndex()) {
394                                 furthestError = parsePosition.getErrorIndex();
395                             }
396                             continue;
397                         }
398                         if (looseMatches(suffix, toParse, parsePosition) && prefixSet.intersects(suffixSet)) {
399                             if (longestMatch < parsePosition.getIndex()) {
400                                 longestMatch = parsePosition.getIndex();
401                                 bestNumber = number;
402                                 bestUnit = getFirst(prefixSet, suffixSet);
403                             }
404                         } else if (furthestError < parsePosition.getErrorIndex()) {
405                             furthestError = parsePosition.getErrorIndex();
406                         } 
407                     } else if (furthestError < parsePosition.getErrorIndex()) {
408                         furthestError = parsePosition.getErrorIndex();
409                     } 
410
411                 }
412             }
413             if (longestMatch >= 0) {
414                 parsePosition.setIndex(longestMatch);
415                 return new Measure(bestNumber, index.getUnit(bestUnit));
416             }
417             parsePosition.setErrorIndex(furthestError);
418             return null;
419         }
420     }
421
422     static class Index<T> {
423         List<T> intToItem = new ArrayList<T>();
424         Map<T,Integer> itemToInt = new HashMap<T,Integer>();
425
426         int getIndex(T item) {
427             return itemToInt.get(item);
428         }
429         T getUnit(int index) {
430             return intToItem.get(index);
431         }
432         int addItem(T item) {
433             Integer index = itemToInt.get(item);
434             if (index != null) {
435                 return index;
436             }
437             int size = intToItem.size();
438             itemToInt.put(item, size);
439             intToItem.add(item);
440             return size;
441         }
442     }
443     
444     /**
445      * @internal
446      * @deprecated This API is ICU internal only.
447      */
448     @Override
449     public Measure parseObject(String toParse, ParsePosition parsePosition) {
450         if (parseData == null) {
451             parseData = ParseData.of(locale, unitToStyleToCountToFormat);
452         }
453         //        int index = parsePosition.getIndex();
454         //        int errorIndex = parsePosition.getIndex();
455         Measure result = parseData.parse(numberFormat, toParse, parsePosition);
456         //        if (result == null) {
457         //            parsePosition.setIndex(index);
458         //            parsePosition.setErrorIndex(errorIndex);
459         //            result = compatCurrencyFormat.parseCurrency(toParse, parsePosition);
460         //        }
461         return result;
462     }
463
464
465     /*
466      * @param prefixSet
467      * @param suffixSet
468      * @return
469      */
470     private static int getFirst(BitSet prefixSet, BitSet suffixSet) {
471         for (int i = prefixSet.nextSetBit(0); i >= 0; i = prefixSet.nextSetBit(i+1)) {
472             if (suffixSet.get(i)) {
473                 return i;
474             }
475         }
476         return 0;
477     }
478
479     /*
480      * @param suffix
481      * @param arg0
482      * @param arg1
483      * @return
484      */
485     // TODO make this lenient
486     private static boolean looseMatches(String suffix, String arg0, ParsePosition arg1) {
487         boolean matches = suffix.regionMatches(0, arg0, arg1.getIndex(), suffix.length());
488         if (matches) {
489             arg1.setErrorIndex(-1);
490             arg1.setIndex(arg1.getIndex() + suffix.length());
491         } else {
492             arg1.setErrorIndex(arg1.getIndex());
493         }
494         return matches;
495     }
496
497     static final Comparator<String> LONGEST_FIRST = new Comparator<String>() {
498         public int compare(String as, String bs) {
499             if (as.length() > bs.length()) {
500                 return -1;
501             }
502             if (as.length() < bs.length()) {
503                 return 1;
504             }
505             return as.compareTo(bs);
506         }
507     };
508
509     /**
510      * @internal
511      * @deprecated This API is ICU internal only.
512      */
513     @Override
514     public boolean equals(Object obj) {
515         if (obj == null || obj.getClass() != GeneralMeasureFormat.class) {
516             return false;
517           }
518         GeneralMeasureFormat other = (GeneralMeasureFormat) obj;
519         return locale.equals(other.locale) 
520                 && length == other.length
521                 && numberFormat.equals(other.numberFormat);
522     }
523     
524     /**
525      * @internal
526      * @deprecated This API is ICU internal only.
527      */
528     @Override
529     public int hashCode() {
530         // TODO Auto-generated method stub
531         return (locale.hashCode() * 37 + length.hashCode()) * 37 + numberFormat.hashCode();
532     }
533     
534     private Object writeReplace() throws ObjectStreamException {
535         return new GeneralMeasureProxy(locale, length, numberFormat);
536     }
537
538     static class GeneralMeasureProxy implements Externalizable {
539         private static final long serialVersionUID = -6033308329886716770L;
540
541         private ULocale locale;
542         private FormatWidth length;
543         private NumberFormat numberFormat;
544
545         public GeneralMeasureProxy(ULocale locale, FormatWidth length, NumberFormat numberFormat) {
546             this.locale = locale;
547             this.length = length;
548             this.numberFormat = numberFormat;
549         }
550
551         // Must have public constructor, to enable Externalizable
552         public GeneralMeasureProxy() {
553         }
554
555         public void writeExternal(ObjectOutput out) throws IOException {
556             out.writeByte(0); // version
557             out.writeObject(locale);
558             out.writeObject(length);
559             out.writeObject(numberFormat);
560             out.writeShort(0); // allow for more data.
561         }
562
563         public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
564             /* byte version = */ in.readByte(); // version
565             locale = (ULocale) in.readObject();
566             length = (FormatWidth) in.readObject();
567             numberFormat = (NumberFormat) in.readObject();
568             // allow for more data from future version
569             int extra = in.readShort();
570             if (extra > 0) {
571                 byte[] extraBytes = new byte[extra];
572                 in.read(extraBytes, 0, extra);
573             }
574         }
575
576         private Object readResolve() throws ObjectStreamException {
577             return GeneralMeasureFormat.getInstance(locale, length, numberFormat);
578         }
579     }
580 }