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