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) 2007-2016, International Business Machines
      6 *   Corporation and others.  All Rights Reserved.
      7 *******************************************************************************
      8 */
      9 package com.ibm.icu.impl;
     10 
     11 import java.io.IOException;
     12 import java.io.ObjectInputStream;
     13 import java.math.BigInteger;
     14 import java.text.FieldPosition;
     15 import java.text.ParsePosition;
     16 import java.util.Arrays;
     17 import java.util.MissingResourceException;
     18 
     19 import com.ibm.icu.lang.UCharacter;
     20 import com.ibm.icu.math.BigDecimal;
     21 import com.ibm.icu.text.NumberFormat;
     22 import com.ibm.icu.util.ULocale;
     23 import com.ibm.icu.util.UResourceBundle;
     24 
     25 /*
     26  * NumberFormat implementation dedicated/optimized for DateFormat,
     27  * used by SimpleDateFormat implementation.
     28  * This class is not thread-safe.
     29  */
     30 public final class DateNumberFormat extends NumberFormat {
     31 
     32     private static final long serialVersionUID = -6315692826916346953L;
     33 
     34     private char[] digits;
     35     private char zeroDigit; // For backwards compatibility
     36     private char minusSign;
     37     private boolean positiveOnly = false;
     38 
     39     private static final int DECIMAL_BUF_SIZE = 20; // 20 digits is good enough to store Long.MAX_VALUE
     40     private transient char[] decimalBuf = new char[DECIMAL_BUF_SIZE];
     41 
     42     private static SimpleCache<ULocale, char[]> CACHE = new SimpleCache<ULocale, char[]>();
     43 
     44     private int maxIntDigits;
     45     private int minIntDigits;
     46 
     47     public DateNumberFormat(ULocale loc, String digitString, String nsName) {
     48         if (digitString.length() > 10) {
     49             throw new UnsupportedOperationException("DateNumberFormat does not support digits out of BMP.");
     50         }
     51         initialize(loc,digitString,nsName);
     52     }
     53 
     54     public DateNumberFormat(ULocale loc, char zeroDigit, String nsName) {
     55         StringBuffer buf = new StringBuffer();
     56         for ( int i = 0 ; i < 10 ; i++ ) {
     57             buf.append((char)(zeroDigit+i));
     58         }
     59         initialize(loc,buf.toString(),nsName);
     60     }
     61 
     62     private void initialize(ULocale loc,String digitString,String nsName) {
     63         char[] elems = CACHE.get(loc);
     64         if (elems == null) {
     65             // Missed cache
     66             String minusString;
     67             ICUResourceBundle rb = (ICUResourceBundle)UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, loc);
     68             try {
     69                 minusString = rb.getStringWithFallback("NumberElements/"+nsName+"/symbols/minusSign");
     70             } catch (MissingResourceException ex) {
     71                 if ( !nsName.equals("latn") ) {
     72                     try {
     73                        minusString = rb.getStringWithFallback("NumberElements/latn/symbols/minusSign");
     74                     } catch (MissingResourceException ex1) {
     75                         minusString = "-";
     76                     }
     77                 } else {
     78                     minusString = "-";
     79                 }
     80             }
     81             elems = new char[11];
     82             for ( int i = 0 ; i < 10 ; i++ ) {
     83                  elems[i] = digitString.charAt(i);
     84             }
     85             elems[10] = minusString.charAt(0);
     86             CACHE.put(loc, elems);
     87         }
     88 
     89         digits = new char[10];
     90         System.arraycopy(elems, 0, digits, 0, 10);
     91         zeroDigit = digits[0];
     92 
     93         minusSign = elems[10];
     94     }
     95 
     96     @Override
     97     public void setMaximumIntegerDigits(int newValue) {
     98         maxIntDigits = newValue;
     99     }
    100 
    101     @Override
    102     public int getMaximumIntegerDigits() {
    103         return maxIntDigits;
    104     }
    105 
    106     @Override
    107     public void setMinimumIntegerDigits(int newValue) {
    108         minIntDigits = newValue;
    109     }
    110 
    111     @Override
    112     public int getMinimumIntegerDigits() {
    113         return minIntDigits;
    114     }
    115 
    116     /* For supporting SimpleDateFormat.parseInt */
    117     public void setParsePositiveOnly(boolean isPositiveOnly) {
    118         positiveOnly = isPositiveOnly;
    119     }
    120 
    121     public char getZeroDigit() {
    122         return zeroDigit;
    123     }
    124 
    125     public void setZeroDigit(char zero) {
    126         zeroDigit = zero;
    127         if (digits == null) {
    128             digits = new char[10];
    129         }
    130         digits[0] = zero;
    131         for ( int i = 1 ; i < 10 ; i++ ) {
    132             digits[i] = (char)(zero+i);
    133         }
    134     }
    135 
    136     public char[] getDigits() {
    137         return digits.clone();
    138     }
    139 
    140     @Override
    141     public StringBuffer format(double number, StringBuffer toAppendTo,
    142             FieldPosition pos) {
    143         throw new UnsupportedOperationException("StringBuffer format(double, StringBuffer, FieldPostion) is not implemented");
    144     }
    145 
    146     @Override
    147     public StringBuffer format(long numberL, StringBuffer toAppendTo,
    148             FieldPosition pos) {
    149 
    150         if (numberL < 0) {
    151             // negative
    152             toAppendTo.append(minusSign);
    153             numberL = -numberL;
    154         }
    155 
    156         // Note: NumberFormat used by DateFormat only uses int numbers.
    157         // Remainder operation on 32bit platform using long is significantly slower
    158         // than int.  So, this method casts long number into int.
    159         int number = (int)numberL;
    160 
    161         int limit = decimalBuf.length < maxIntDigits ? decimalBuf.length : maxIntDigits;
    162         int index = limit - 1;
    163         while (true) {
    164             decimalBuf[index] = digits[(number % 10)];
    165             number /= 10;
    166             if (index == 0 || number == 0) {
    167                 break;
    168             }
    169             index--;
    170         }
    171         int padding = minIntDigits - (limit - index);
    172         for (; padding > 0; padding--) {
    173             decimalBuf[--index] = digits[0];
    174         }
    175         int length = limit - index;
    176         toAppendTo.append(decimalBuf, index, length);
    177         pos.setBeginIndex(0);
    178         if (pos.getField() == NumberFormat.INTEGER_FIELD) {
    179             pos.setEndIndex(length);
    180         } else {
    181             pos.setEndIndex(0);
    182         }
    183         return toAppendTo;
    184     }
    185 
    186     @Override
    187     public StringBuffer format(BigInteger number, StringBuffer toAppendTo,
    188             FieldPosition pos) {
    189         throw new UnsupportedOperationException("StringBuffer format(BigInteger, StringBuffer, FieldPostion) is not implemented");
    190     }
    191 
    192     @Override
    193     public StringBuffer format(java.math.BigDecimal number, StringBuffer toAppendTo,
    194             FieldPosition pos) {
    195         throw new UnsupportedOperationException("StringBuffer format(BigDecimal, StringBuffer, FieldPostion) is not implemented");
    196     }
    197 
    198     @Override
    199     public StringBuffer format(BigDecimal number,
    200             StringBuffer toAppendTo, FieldPosition pos) {
    201         throw new UnsupportedOperationException("StringBuffer format(BigDecimal, StringBuffer, FieldPostion) is not implemented");
    202     }
    203 
    204     /*
    205      * Note: This method only parse integer numbers which can be represented by long
    206      */
    207     private static final long PARSE_THRESHOLD = 922337203685477579L; // (Long.MAX_VALUE / 10) - 1
    208 
    209     @Override
    210     public Number parse(String text, ParsePosition parsePosition) {
    211         long num = 0;
    212         boolean sawNumber = false;
    213         boolean negative = false;
    214         int base = parsePosition.getIndex();
    215         int offset = 0;
    216         for (; base + offset < text.length(); offset++) {
    217             char ch = text.charAt(base + offset);
    218             if (offset == 0 && ch == minusSign) {
    219                 if (positiveOnly) {
    220                     break;
    221                 }
    222                 negative = true;
    223             } else {
    224                 int digit = ch - digits[0];
    225                 if (digit < 0 || 9 < digit) {
    226                     digit = UCharacter.digit(ch);
    227                 }
    228                 if (digit < 0 || 9 < digit) {
    229                     for ( digit = 0 ; digit < 10 ; digit++ ) {
    230                         if ( ch == digits[digit]) {
    231                             break;
    232                         }
    233                     }
    234                 }
    235                 if (0 <= digit && digit <= 9 && num < PARSE_THRESHOLD) {
    236                     sawNumber = true;
    237                     num = num * 10 + digit;
    238                 } else {
    239                     break;
    240                 }
    241             }
    242         }
    243         Number result = null;
    244         if (sawNumber) {
    245             num = negative ? num * (-1) : num;
    246             result = Long.valueOf(num);
    247             parsePosition.setIndex(base + offset);
    248         }
    249         return result;
    250     }
    251 
    252     @Override
    253     public boolean equals(Object obj) {
    254         if (obj == null || !super.equals(obj) || !(obj instanceof DateNumberFormat)) {
    255             return false;
    256         }
    257         DateNumberFormat other = (DateNumberFormat)obj;
    258         return (this.maxIntDigits == other.maxIntDigits
    259                 && this.minIntDigits == other.minIntDigits
    260                 && this.minusSign == other.minusSign
    261                 && this.positiveOnly == other.positiveOnly
    262                 && Arrays.equals(this.digits, other.digits));
    263     }
    264 
    265     @Override
    266     public int hashCode() {
    267         return super.hashCode();
    268     }
    269 
    270     private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
    271         stream.defaultReadObject();
    272         if (digits == null) {
    273             setZeroDigit(zeroDigit);
    274         }
    275         // re-allocate the work buffer
    276         decimalBuf = new char[DECIMAL_BUF_SIZE];
    277     }
    278 
    279     @Override
    280     public Object clone() {
    281         DateNumberFormat dnfmt = (DateNumberFormat)super.clone();
    282         dnfmt.digits = this.digits.clone();
    283         dnfmt.decimalBuf = new char[DECIMAL_BUF_SIZE];
    284         return dnfmt;
    285     }
    286 }
    287 
    288 //eof
    289