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