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