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