]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-52_1/main/classes/core/src/com/ibm/icu/impl/duration/impl/PeriodFormatterData.java
Clean up imports.
[Dictionary.git] / jars / icu4j-52_1 / main / classes / core / src / com / ibm / icu / impl / duration / impl / PeriodFormatterData.java
1 /*
2 ******************************************************************************
3 * Copyright (C) 2009-2011, International Business Machines Corporation and   *
4 * others. All Rights Reserved.                                               *
5 ******************************************************************************
6 */
7
8 package com.ibm.icu.impl.duration.impl;
9
10 import java.util.Arrays;
11
12 import com.ibm.icu.impl.duration.TimeUnit;
13 import com.ibm.icu.impl.duration.impl.DataRecord.ECountVariant;
14 import com.ibm.icu.impl.duration.impl.DataRecord.EDecimalHandling;
15 import com.ibm.icu.impl.duration.impl.DataRecord.EFractionHandling;
16 import com.ibm.icu.impl.duration.impl.DataRecord.EGender;
17 import com.ibm.icu.impl.duration.impl.DataRecord.EHalfPlacement;
18 import com.ibm.icu.impl.duration.impl.DataRecord.EHalfSupport;
19 import com.ibm.icu.impl.duration.impl.DataRecord.ENumberSystem;
20 import com.ibm.icu.impl.duration.impl.DataRecord.EPluralization;
21 import com.ibm.icu.impl.duration.impl.DataRecord.EUnitVariant;
22 import com.ibm.icu.impl.duration.impl.DataRecord.EZeroHandling;
23 import com.ibm.icu.impl.duration.impl.DataRecord.ScopeData;
24
25
26 /**
27  * PeriodFormatterData provides locale-specific data used to format
28  * relative dates and times, and convenience api to access it.
29  *
30  * An instance of PeriodFormatterData is usually created by requesting
31  * data for a given locale from an PeriodFormatterDataService.
32  */
33 public class PeriodFormatterData {
34   final DataRecord dr;
35   String localeName;
36
37   // debug
38   public static boolean trace = false;
39
40   public PeriodFormatterData(String localeName, DataRecord dr) {
41     this.dr = dr;
42     this.localeName = localeName;
43     if(localeName == null) {
44         throw new NullPointerException("localename is null");
45     }
46 //    System.err.println("** localeName is " + localeName);
47     if (dr == null) {
48 //      Thread.dumpStack();
49       throw new NullPointerException("data record is null");
50     }
51   }
52
53   // none - chinese (all forms the same)
54   // plural - english, special form for 1
55   // dual - special form for 1 and 2
56   // paucal - russian, special form for 1, for 2-4 and n > 20 && n % 10 == 2-4
57   // rpt_dual_few - slovenian, special form for 1, 2, 3-4 and n as above
58   // hebrew, dual plus singular form for years > 11
59   // arabic, dual, plus singular form for all terms > 10
60
61   /**
62    * Return the pluralization format used by this locale.
63    * @return the pluralization format
64    */
65   public int pluralization() {
66     return dr.pl;
67   }
68
69   /**
70    * Return true if zeros are allowed in the display.
71    * @return true if zeros should be allowed
72    */
73   public boolean allowZero() {
74     return dr.allowZero;
75   }
76
77   public boolean weeksAloneOnly() {
78     return dr.weeksAloneOnly;
79   }
80
81   public int useMilliseconds() {
82     return dr.useMilliseconds;
83   }
84
85   /**
86    * Append the appropriate prefix to the string builder, depending on whether and
87    * how a limit and direction are to be displayed.
88    *
89    * @param tl how and whether to display the time limit
90    * @param td how and whether to display the time direction
91    * @param sb the string builder to which to append the text
92    * @return true if a following digit will require a digit prefix
93    */
94   public boolean appendPrefix(int tl, int td, StringBuffer sb) {
95     if (dr.scopeData != null) {
96       int ix = tl * 3 + td;
97       ScopeData sd = dr.scopeData[ix];
98       if (sd != null) {
99         String prefix = sd.prefix;
100         if (prefix != null) {
101           sb.append(prefix);
102           return sd.requiresDigitPrefix;
103         }
104       }
105     }
106     return false;
107   }
108
109   /**
110    * Append the appropriate suffix to the string builder, depending on whether and
111    * how a limit and direction are to be displayed.
112    *
113    * @param tl how and whether to display the time limit
114    * @param td how and whether to display the time direction
115    * @param sb the string builder to which to append the text
116    */
117   public void appendSuffix(int tl, int td, StringBuffer sb) {
118     if (dr.scopeData != null) {
119       int ix = tl * 3 + td;
120       ScopeData sd = dr.scopeData[ix];
121       if (sd != null) {
122         String suffix = sd.suffix;
123         if (suffix != null) {
124           if (trace) {
125             System.out.println("appendSuffix '" + suffix + "'");
126           }
127           sb.append(suffix);
128         }
129       }
130     }
131   }
132
133   /**
134    * Append the count and unit to the string builder.
135    *
136    * @param unit the unit to append
137    * @param count the count of units, * 1000
138    * @param cv the format to use for displaying the count
139    * @param uv the format to use for displaying the unit
140    * @param useCountSep if false, force no separator between count and unit
141    * @param useDigitPrefix if true, use the digit prefix
142    * @param multiple true if there are multiple units in this string
143    * @param last true if this is the last unit
144    * @param wasSkipped true if the unit(s) before this were skipped
145    * @param sb the string builder to which to append the text
146    * @return true if will require skip marker
147    */
148   @SuppressWarnings("fallthrough")
149   public boolean appendUnit(TimeUnit unit, int count, int cv, 
150                             int uv, boolean useCountSep, 
151                             boolean useDigitPrefix, boolean multiple, 
152                             boolean last, boolean wasSkipped, 
153                             StringBuffer sb) {
154     int px = unit.ordinal();
155
156     boolean willRequireSkipMarker = false;
157     if (dr.requiresSkipMarker != null && dr.requiresSkipMarker[px] && 
158         dr.skippedUnitMarker != null) {
159       if (!wasSkipped && last) {
160         sb.append(dr.skippedUnitMarker);
161       }
162       willRequireSkipMarker = true;
163     }
164
165     if (uv != EUnitVariant.PLURALIZED) {
166       boolean useMedium = uv == EUnitVariant.MEDIUM; 
167       String[] names = useMedium ? dr.mediumNames : dr.shortNames;
168       if (names == null || names[px] == null) {
169         names = useMedium ? dr.shortNames : dr.mediumNames;
170       }
171       if (names != null && names[px] != null) {
172         appendCount(unit, false, false, count, cv, useCountSep, 
173                     names[px], last, sb); // omit suffix, ok?
174         return false; // omit skip marker
175       }
176     }
177
178     // check cv
179     if (cv == ECountVariant.HALF_FRACTION && dr.halfSupport != null) {
180       switch (dr.halfSupport[px]) {
181         case EHalfSupport.YES: break;
182         case EHalfSupport.ONE_PLUS:
183           if (count > 1000) {
184             break;
185           }
186           // else fall through to decimal
187         case EHalfSupport.NO: {
188           count = (count / 500) * 500;  // round to 1/2
189           cv = ECountVariant.DECIMAL1; 
190         } break;
191       }
192     }
193           
194     String name = null;
195     int form = computeForm(unit, count, cv, multiple && last);
196     if (form == FORM_SINGULAR_SPELLED) {
197       if (dr.singularNames == null) {
198         form = FORM_SINGULAR;
199         name = dr.pluralNames[px][form];
200       } else {
201         name = dr.singularNames[px];
202       }
203     } else if (form == FORM_SINGULAR_NO_OMIT) {
204       name = dr.pluralNames[px][FORM_SINGULAR];
205     } else if (form == FORM_HALF_SPELLED) {
206       name = dr.halfNames[px];
207     } else { 
208       try {
209         name = dr.pluralNames[px][form];
210       } catch (NullPointerException e) {
211         System.out.println("Null Pointer in PeriodFormatterData["+localeName+"].au px: " + px + " form: " + form + " pn: " + Arrays.toString(dr.pluralNames));
212         throw e;
213       }
214     }
215     if (name == null) {
216       form = FORM_PLURAL;
217       name = dr.pluralNames[px][form];
218     }
219
220     boolean omitCount =
221       (form == FORM_SINGULAR_SPELLED || form == FORM_HALF_SPELLED) ||
222       (dr.omitSingularCount && form == FORM_SINGULAR) ||
223       (dr.omitDualCount && form == FORM_DUAL);
224
225     int suffixIndex = appendCount(unit, omitCount, useDigitPrefix, count, cv, 
226                                   useCountSep, name, last, sb);
227     if (last && suffixIndex >= 0) {
228       String suffix = null;
229       if (dr.rqdSuffixes != null && suffixIndex < dr.rqdSuffixes.length) {
230         suffix = dr.rqdSuffixes[suffixIndex];
231       }
232       if (suffix == null && dr.optSuffixes != null && 
233           suffixIndex < dr.optSuffixes.length) {
234         suffix = dr.optSuffixes[suffixIndex];
235       }
236       if (suffix != null) {
237         sb.append(suffix);
238       }
239     }
240     return willRequireSkipMarker;
241   }
242
243   /**
244    * Append a count to the string builder.
245    *
246    * @param unit the unit
247    * @param count the count
248    * @param cv the format to use for displaying the count
249    * @param useSep whether to use the count separator, if available
250    * @param name the term name
251    * @param last true if this is the last unit to be formatted
252    * @param sb the string builder to which to append the text
253    * @return index to use if might have required or optional suffix, or -1 if none required
254    */
255   public int appendCount(TimeUnit unit, boolean omitCount, 
256                          boolean useDigitPrefix, 
257                          int count, int cv, boolean useSep, 
258                          String name, boolean last, StringBuffer sb) {
259     if (cv == ECountVariant.HALF_FRACTION && dr.halves == null) {
260       cv = ECountVariant.INTEGER;
261     }
262
263     if (!omitCount && useDigitPrefix && dr.digitPrefix != null) {
264       sb.append(dr.digitPrefix);
265     }
266
267     int index = unit.ordinal();
268     switch (cv) {
269       case ECountVariant.INTEGER: {
270         if (!omitCount) {
271           appendInteger(count/1000, 1, 10, sb);
272         }
273       } break;
274
275       case ECountVariant.INTEGER_CUSTOM: {
276         int val = count / 1000;
277         // only custom names we have for now
278         if (unit == TimeUnit.MINUTE && 
279             (dr.fiveMinutes != null || dr.fifteenMinutes != null)) {
280           if (val != 0 && val % 5 == 0) {
281             if (dr.fifteenMinutes != null && (val == 15 || val == 45)) {
282               val = val == 15 ? 1 : 3;
283               if (!omitCount) appendInteger(val, 1, 10, sb);
284               name = dr.fifteenMinutes;
285               index = 8; // hack
286               break;
287             }
288             if (dr.fiveMinutes != null) {
289               val = val / 5;
290               if (!omitCount) appendInteger(val, 1, 10, sb);
291               name = dr.fiveMinutes;
292               index = 9; // hack
293               break;
294             }
295           }
296         }
297         if (!omitCount) appendInteger(val, 1, 10, sb);
298       } break;
299
300       case ECountVariant.HALF_FRACTION: {
301         // 0, 1/2, 1, 1-1/2...
302         int v = count / 500;
303         if (v != 1) {
304           if (!omitCount) appendCountValue(count, 1, 0, sb);
305         }
306         if ((v & 0x1) == 1) {
307           // hack, using half name
308           if (v == 1 && dr.halfNames != null && dr.halfNames[index] != null) {
309             sb.append(name);
310             return last ? index : -1;
311           }
312
313           int solox = v == 1 ? 0 : 1;
314           if (dr.genders != null && dr.halves.length > 2) {
315             if (dr.genders[index] == EGender.F) {
316               solox += 2;
317             }
318           }
319           int hp = dr.halfPlacements == null 
320               ? EHalfPlacement.PREFIX
321               : dr.halfPlacements[solox & 0x1];
322           String half = dr.halves[solox];
323           String measure = dr.measures == null ? null : dr.measures[index];
324           switch (hp) {
325             case EHalfPlacement.PREFIX:
326               sb.append(half);
327               break;
328             case EHalfPlacement.AFTER_FIRST: {
329               if (measure != null) {
330                 sb.append(measure);
331                 sb.append(half);
332                 if (useSep && !omitCount) {
333                   sb.append(dr.countSep);
334                 } 
335                 sb.append(name);
336               } else { // ignore sep completely
337                 sb.append(name);
338                 sb.append(half);
339                 return last ? index : -1; // might use suffix
340               }
341             } return -1; // exit early
342             case EHalfPlacement.LAST: {
343               if (measure != null) {
344                 sb.append(measure);
345               }
346               if (useSep && !omitCount) {
347                 sb.append(dr.countSep);
348               }
349               sb.append(name);
350               sb.append(half);
351             } return last ? index : -1; // might use suffix
352           }
353         }
354       } break;
355       default: {
356         int decimals = 1;
357         switch (cv) {
358           case ECountVariant.DECIMAL2: decimals = 2; break;
359           case ECountVariant.DECIMAL3: decimals = 3; break;
360           default: break;
361         }
362         if (!omitCount) appendCountValue(count, 1, decimals, sb);
363       } break;
364     }
365     if (!omitCount && useSep) {
366       sb.append(dr.countSep);
367     }
368     if (!omitCount && dr.measures != null && index < dr.measures.length) {
369       String measure = dr.measures[index];
370       if (measure != null) {
371         sb.append(measure);
372       }
373     }
374     sb.append(name);
375     return last ? index : -1;
376   }
377
378   /**
379    * Append a count value to the builder.
380    *
381    * @param count the count
382    * @param integralDigits the number of integer digits to display
383    * @param decimalDigits the number of decimal digits to display, <= 3
384    * @param sb the string builder to which to append the text
385    */
386   public void appendCountValue(int count, int integralDigits, 
387                                int decimalDigits, StringBuffer sb) {
388     int ival = count / 1000;
389     if (decimalDigits == 0) {
390       appendInteger(ival, integralDigits, 10, sb);
391       return;
392     }
393
394     if (dr.requiresDigitSeparator && sb.length() > 0) {
395       sb.append(' ');
396     }
397     appendDigits(ival, integralDigits, 10, sb);
398     int dval = count % 1000;
399     if (decimalDigits == 1) {
400       dval /= 100;
401     } else if (decimalDigits == 2) {
402       dval /= 10;
403     }
404     sb.append(dr.decimalSep);
405     appendDigits(dval, decimalDigits, decimalDigits, sb);
406     if (dr.requiresDigitSeparator) {
407       sb.append(' ');
408     }
409   }
410
411   public void appendInteger(int num, int mindigits, int maxdigits, 
412                             StringBuffer sb) {
413     if (dr.numberNames != null && num < dr.numberNames.length) {
414       String name = dr.numberNames[num];
415       if (name != null) {
416         sb.append(name);
417         return;
418       }
419     }
420
421     if (dr.requiresDigitSeparator && sb.length() > 0) {
422       sb.append(' ');
423     }
424     switch (dr.numberSystem) {
425       case ENumberSystem.DEFAULT: appendDigits(num, mindigits, maxdigits, sb); break;
426       case ENumberSystem.CHINESE_TRADITIONAL: sb.append(
427           Utils.chineseNumber(num, Utils.ChineseDigits.TRADITIONAL)); break;
428       case ENumberSystem.CHINESE_SIMPLIFIED: sb.append(
429           Utils.chineseNumber(num, Utils.ChineseDigits.SIMPLIFIED)); break;
430       case ENumberSystem.KOREAN: sb.append(
431           Utils.chineseNumber(num, Utils.ChineseDigits.KOREAN)); break;
432     }
433     if (dr.requiresDigitSeparator) {
434       sb.append(' ');
435     }
436   }
437
438   /**
439    * Append digits to the string builder, using this.zero for '0' etc.
440    *
441    * @param num the integer to append
442    * @param mindigits the minimum number of digits to append
443    * @param maxdigits the maximum number of digits to append
444    * @param sb the string builder to which to append the text
445    */
446   public void appendDigits(long num, int mindigits, int maxdigits,  
447                            StringBuffer sb) {
448     char[] buf = new char[maxdigits];
449     int ix = maxdigits;
450     while (ix > 0 && num > 0) {
451       buf[--ix] = (char)(dr.zero + (num % 10));
452       num /= 10;
453     }
454     for (int e = maxdigits - mindigits; ix > e;) {
455       buf[--ix] = dr.zero;
456     }
457     sb.append(buf, ix, maxdigits - ix);
458   }
459
460   /**
461    * Append a marker for skipped units internal to a string.
462    * @param sb the string builder to which to append the text
463    */
464   public void appendSkippedUnit(StringBuffer sb) {
465     if (dr.skippedUnitMarker != null) {
466       sb.append(dr.skippedUnitMarker);
467     }
468   }
469
470   /**
471    * Append the appropriate separator between units
472    *
473    * @param unit the unit to which to append the separator
474    * @param afterFirst true if this is the first unit formatted
475    * @param beforeLast true if this is the next-to-last unit to be formatted
476    * @param sb the string builder to which to append the text
477    * @return true if a prefix will be required before a following unit
478    */
479   public boolean appendUnitSeparator(TimeUnit unit, boolean longSep, 
480                                      boolean afterFirst, boolean beforeLast, 
481                                      StringBuffer sb) {
482     // long seps
483     // false, false "...b', '...d"
484     // false, true  "...', and 'c"
485     // true, false - "a', '...c"
486     // true, true - "a' and 'b"
487     if ((longSep && dr.unitSep != null) || dr.shortUnitSep != null) {
488       if (longSep && dr.unitSep != null) {
489         int ix = (afterFirst ? 2 : 0) + (beforeLast ? 1 : 0);
490         sb.append(dr.unitSep[ix]);
491         return dr.unitSepRequiresDP != null && dr.unitSepRequiresDP[ix];
492       }
493       sb.append(dr.shortUnitSep); // todo: investigate whether DP is required
494     }
495     return false;
496   }
497
498   private static final int 
499     FORM_PLURAL = 0,
500     FORM_SINGULAR = 1,
501     FORM_DUAL = 2,
502     FORM_PAUCAL = 3,
503     FORM_SINGULAR_SPELLED = 4, // following are not in the pluralization list
504     FORM_SINGULAR_NO_OMIT = 5, // a hack
505     FORM_HALF_SPELLED = 6;
506
507   private int computeForm(TimeUnit unit, int count, int cv, 
508                           boolean lastOfMultiple) {
509     // first check if a particular form is forced by the countvariant.  if
510     // SO, just return that.  otherwise convert the count to an integer
511     // and use pluralization rules to determine which form to use.
512     // careful, can't assume any forms but plural exist.
513
514     if (trace) {
515       System.err.println("pfd.cf unit: " + unit + " count: " + count + " cv: " + cv + " dr.pl: " + dr.pl);
516       Thread.dumpStack();
517     }
518     if (dr.pl == EPluralization.NONE) {
519       return FORM_PLURAL;
520     }
521     // otherwise, assume we have at least a singular and plural form
522
523     int val = count/1000;
524
525     switch (cv) {
526       case ECountVariant.INTEGER: 
527       case ECountVariant.INTEGER_CUSTOM: {
528         // do more analysis based on floor of count
529       } break;
530       case ECountVariant.HALF_FRACTION: {
531         switch (dr.fractionHandling) {
532           case EFractionHandling.FPLURAL:
533             return FORM_PLURAL;
534
535           case EFractionHandling.FSINGULAR_PLURAL_ANDAHALF:
536           case EFractionHandling.FSINGULAR_PLURAL: {
537             // if half-floor is 1/2, use singular
538             // else if half-floor is not integral, use plural
539             // else do more analysis
540             int v = count / 500;
541             if (v == 1) {
542               if (dr.halfNames != null && dr.halfNames[unit.ordinal()] != null) {
543                 return FORM_HALF_SPELLED;
544               }
545               return FORM_SINGULAR_NO_OMIT;
546             }
547             if ((v & 0x1) == 1) {
548               if (dr.pl == EPluralization.ARABIC && v > 21) { // hack
549                 return FORM_SINGULAR_NO_OMIT;
550               }
551               if (v == 3 && dr.pl == EPluralization.PLURAL &&
552                   dr.fractionHandling != EFractionHandling.FSINGULAR_PLURAL_ANDAHALF) {
553                 return FORM_PLURAL;
554               }
555             }
556             
557             // it will display like an integer, so do more analysis
558           } break;
559
560           case EFractionHandling.FPAUCAL: {
561             int v = count / 500;
562             if (v == 1 || v == 3) {
563               return FORM_PAUCAL;
564             }
565             // else use integral form
566           } break;
567
568           default:
569             throw new IllegalStateException();
570         }
571       } break;
572       default: { // for all decimals
573         switch (dr.decimalHandling) {
574           case EDecimalHandling.DPLURAL: break;
575           case EDecimalHandling.DSINGULAR: return FORM_SINGULAR_NO_OMIT;
576           case EDecimalHandling.DSINGULAR_SUBONE:
577             if (count < 1000) {
578               return FORM_SINGULAR_NO_OMIT;
579             }
580             break;
581           case EDecimalHandling.DPAUCAL:
582             if (dr.pl == EPluralization.PAUCAL) {
583               return FORM_PAUCAL;
584             }
585             break;
586           default:
587             break;
588         }
589         return FORM_PLURAL;
590       }
591     }
592
593     // select among pluralization forms
594     if (trace && count == 0) {
595       System.err.println("EZeroHandling = " + dr.zeroHandling);
596     }
597     if (count == 0 && dr.zeroHandling == EZeroHandling.ZSINGULAR) {
598       return FORM_SINGULAR_SPELLED;
599     }
600
601     int form = FORM_PLURAL;
602     switch(dr.pl) {
603       case EPluralization.NONE: break; // never get here
604       case EPluralization.PLURAL: {
605         if (val == 1) { 
606           form = FORM_SINGULAR_SPELLED; // defaults to form_singular if no spelled forms
607         } 
608       } break;
609       case EPluralization.DUAL: {
610         if (val == 2) {
611           form = FORM_DUAL; 
612         } else if (val == 1) {
613           form = FORM_SINGULAR; 
614         } 
615       } break;
616       case EPluralization.PAUCAL: {
617         int v = val;
618         v = v % 100;
619         if (v > 20) {
620           v = v % 10;
621         }
622         if (v == 1) {
623           form = FORM_SINGULAR;
624         } else if (v > 1 && v < 5) {
625           form = FORM_PAUCAL;
626         }
627       } break;
628         /*
629       case EPluralization.RPT_DUAL_FEW: {
630         int v = val;
631         if (v > 20) {
632           v = v % 10;
633         }
634         if (v == 1) {
635           form = FORM_SINGULAR;
636         } else if (v == 2) {
637           form = FORM_DUAL;
638         } else if (v > 2 && v < 5) {
639           form = FORM_PAUCAL;
640         }
641       } break;
642         */
643       case EPluralization.HEBREW: {
644         if (val == 2) {
645           form = FORM_DUAL;
646         } else if (val == 1) {
647           if (lastOfMultiple) {
648             form = FORM_SINGULAR_SPELLED;
649           } else {
650             form = FORM_SINGULAR;
651           } 
652         } else if (unit == TimeUnit.YEAR && val > 11) {
653           form = FORM_SINGULAR_NO_OMIT;
654         }
655       } break;
656       case EPluralization.ARABIC: {
657         if (val == 2) {
658           form = FORM_DUAL;
659         } else if (val == 1) {
660           form = FORM_SINGULAR;
661         } else if (val > 10) {
662           form = FORM_SINGULAR_NO_OMIT;
663         }
664       } break;
665       default: 
666         System.err.println("dr.pl is " + dr.pl);
667         throw new IllegalStateException();
668     }
669
670     return form;
671   }
672 }