1/* GENERATED SOURCE. DO NOT MODIFY. */ 2/* 3 ******************************************************************************* 4 * Copyright (C) 1996-2014, Google, International Business Machines Corporation and 5 * others. All Rights Reserved. * 6 ******************************************************************************* 7 */ 8 9package android.icu.text; 10 11import java.io.IOException; 12import java.io.NotSerializableException; 13import java.io.ObjectInputStream; 14import java.io.ObjectOutputStream; 15import java.math.BigDecimal; 16import java.math.BigInteger; 17import java.text.AttributedCharacterIterator; 18import java.text.FieldPosition; 19import java.text.ParsePosition; 20import java.util.Arrays; 21import java.util.Collection; 22import java.util.HashMap; 23import java.util.Locale; 24import java.util.Map; 25import java.util.Map.Entry; 26 27import android.icu.text.CompactDecimalDataCache.Data; 28import android.icu.text.PluralRules.FixedDecimal; 29import android.icu.util.Output; 30import android.icu.util.ULocale; 31 32/** 33 * The CompactDecimalFormat produces abbreviated numbers, suitable for display in environments will limited real estate. 34 * For example, 'Hits: 1.2B' instead of 'Hits: 1,200,000,000'. The format will be appropriate for the given language, 35 * such as "1,2 Mrd." for German. 36 * <p> 37 * For numbers under 1000 trillion (under 10^15, such as 123,456,789,012,345), the result will be short for supported 38 * languages. However, the result may sometimes exceed 7 characters, such as when there are combining marks or thin 39 * characters. In such cases, the visual width in fonts should still be short. 40 * <p> 41 * By default, there are 2 significant digits. After creation, if more than three significant digits are set (with 42 * setMaximumSignificantDigits), or if a fixed number of digits are set (with setMaximumIntegerDigits or 43 * setMaximumFractionDigits), then result may be wider. 44 * <p> 45 * At this time, negative numbers and parsing are not supported, and will produce an UnsupportedOperationException. 46 * Resetting the pattern prefixes or suffixes is not supported; the method calls are ignored. 47 * <p> 48 * Note that important methods, like setting the number of decimals, will be moved up from DecimalFormat to 49 * NumberFormat. 50 * 51 * @author markdavis 52 */ 53public class CompactDecimalFormat extends DecimalFormat { 54 55 private static final long serialVersionUID = 4716293295276629682L; 56 57// private static final int POSITIVE_PREFIX = 0, POSITIVE_SUFFIX = 1, AFFIX_SIZE = 2; 58 private static final CompactDecimalDataCache cache = new CompactDecimalDataCache(); 59 60 private final Map<String, DecimalFormat.Unit[]> units; 61 private final long[] divisor; 62 private final Map<String, Unit> pluralToCurrencyAffixes; 63 64 // null if created internally using explicit prefixes and suffixes. 65 private final PluralRules pluralRules; 66 67 /** 68 * Style parameter for CompactDecimalFormat. 69 */ 70 public enum CompactStyle { 71 /** 72 * Short version, like "1.2T" 73 */ 74 SHORT, 75 /** 76 * Longer version, like "1.2 trillion", if available. May return same result as SHORT if not. 77 */ 78 LONG 79 } 80 81 /** 82 * Create a CompactDecimalFormat appropriate for a locale. The result may 83 * be affected by the number system in the locale, such as ar-u-nu-latn. 84 * 85 * @param locale the desired locale 86 * @param style the compact style 87 */ 88 public static CompactDecimalFormat getInstance(ULocale locale, CompactStyle style) { 89 return new CompactDecimalFormat(locale, style); 90 } 91 92 /** 93 * Create a CompactDecimalFormat appropriate for a locale. The result may 94 * be affected by the number system in the locale, such as ar-u-nu-latn. 95 * 96 * @param locale the desired locale 97 * @param style the compact style 98 */ 99 public static CompactDecimalFormat getInstance(Locale locale, CompactStyle style) { 100 return new CompactDecimalFormat(ULocale.forLocale(locale), style); 101 } 102 103 /** 104 * The public mechanism is CompactDecimalFormat.getInstance(). 105 * 106 * @param locale 107 * the desired locale 108 * @param style 109 * the compact style 110 */ 111 CompactDecimalFormat(ULocale locale, CompactStyle style) { 112 this.pluralRules = PluralRules.forLocale(locale); 113 DecimalFormat format = (DecimalFormat) NumberFormat.getInstance(locale); 114 CompactDecimalDataCache.Data data = getData(locale, style); 115 this.units = data.units; 116 this.divisor = data.divisors; 117 pluralToCurrencyAffixes = null; 118 119// DecimalFormat currencyFormat = (DecimalFormat) NumberFormat.getCurrencyInstance(locale); 120// // TODO fix to use plural-dependent affixes 121// Unit currency = new Unit(currencyFormat.getPositivePrefix(), currencyFormat.getPositiveSuffix()); 122// pluralToCurrencyAffixes = new HashMap<String,Unit>(); 123// for (String key : pluralRules.getKeywords()) { 124// pluralToCurrencyAffixes.put(key, currency); 125// } 126// // TODO fix to get right symbol for the count 127 128 finishInit(style, format.toPattern(), format.getDecimalFormatSymbols()); 129 } 130 131 /** 132 * Create a short number "from scratch". Intended for internal use. The prefix, suffix, and divisor arrays are 133 * parallel, and provide the information for each power of 10. When formatting a value, the correct power of 10 is 134 * found, then the value is divided by the divisor, and the prefix and suffix are set (using 135 * setPositivePrefix/Suffix). 136 * 137 * @param pattern 138 * A number format pattern. Note that the prefix and suffix are discarded, and the decimals are 139 * overridden by default. 140 * @param formatSymbols 141 * Decimal format symbols, typically from a locale. 142 * @param style 143 * compact style. 144 * @param divisor 145 * An array of prefix values, one for each power of 10 from 0 to 14 146 * @param pluralAffixes 147 * A map from plural categories to affixes. 148 * @param currencyAffixes 149 * A map from plural categories to currency affixes. 150 * @param debugCreationErrors 151 * A collection of strings for debugging. If null on input, then any errors found will be added to that 152 * collection instead of throwing exceptions. 153 * @deprecated This API is ICU internal only. 154 * @hide draft / provisional / internal are hidden on Android 155 */ 156 @Deprecated 157 public CompactDecimalFormat(String pattern, DecimalFormatSymbols formatSymbols, 158 CompactStyle style, PluralRules pluralRules, 159 long[] divisor, Map<String,String[][]> pluralAffixes, Map<String, String[]> currencyAffixes, 160 Collection<String> debugCreationErrors) { 161 162 this.pluralRules = pluralRules; 163 this.units = otherPluralVariant(pluralAffixes, divisor, debugCreationErrors); 164 if (!pluralRules.getKeywords().equals(this.units.keySet())) { 165 debugCreationErrors.add("Missmatch in pluralCategories, should be: " + pluralRules.getKeywords() + ", was actually " + this.units.keySet()); 166 } 167 this.divisor = divisor.clone(); 168 if (currencyAffixes == null) { 169 pluralToCurrencyAffixes = null; 170 } else { 171 pluralToCurrencyAffixes = new HashMap<String,Unit>(); 172 for (Entry<String, String[]> s : currencyAffixes.entrySet()) { 173 String[] pair = s.getValue(); 174 pluralToCurrencyAffixes.put(s.getKey(), new Unit(pair[0], pair[1])); 175 } 176 } 177 finishInit(style, pattern, formatSymbols); 178 } 179 180 private void finishInit(CompactStyle style, String pattern, DecimalFormatSymbols formatSymbols) { 181 applyPattern(pattern); 182 setDecimalFormatSymbols(formatSymbols); 183 setMaximumSignificantDigits(2); // default significant digits 184 setSignificantDigitsUsed(true); 185 if (style == CompactStyle.SHORT) { 186 setGroupingUsed(false); 187 } 188 setCurrency(null); 189 } 190 191 /** 192 * {@inheritDoc} 193 */ 194 @Override 195 public boolean equals(Object obj) { 196 if (obj == null) 197 return false; 198 if (!super.equals(obj)) 199 return false; // super does class check 200 CompactDecimalFormat other = (CompactDecimalFormat) obj; 201 return mapsAreEqual(units, other.units) 202 && Arrays.equals(divisor, other.divisor) 203 && (pluralToCurrencyAffixes == other.pluralToCurrencyAffixes 204 || pluralToCurrencyAffixes != null && pluralToCurrencyAffixes.equals(other.pluralToCurrencyAffixes)) 205 && pluralRules.equals(other.pluralRules); 206 } 207 208 private boolean mapsAreEqual( 209 Map<String, DecimalFormat.Unit[]> lhs, Map<String, DecimalFormat.Unit[]> rhs) { 210 if (lhs.size() != rhs.size()) { 211 return false; 212 } 213 // For each MapEntry in lhs, see if there is a matching one in rhs. 214 for (Map.Entry<String, DecimalFormat.Unit[]> entry : lhs.entrySet()) { 215 DecimalFormat.Unit[] value = rhs.get(entry.getKey()); 216 if (value == null || !Arrays.equals(entry.getValue(), value)) { 217 return false; 218 } 219 } 220 return true; 221 } 222 223 /** 224 * {@inheritDoc} 225 */ 226 @Override 227 public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) { 228 Output<Unit> currencyUnit = new Output<Unit>(); 229 Amount amount = toAmount(number, currencyUnit); 230 if (currencyUnit.value != null) { 231 currencyUnit.value.writePrefix(toAppendTo); 232 } 233 Unit unit = amount.getUnit(); 234 unit.writePrefix(toAppendTo); 235 super.format(amount.getQty(), toAppendTo, pos); 236 unit.writeSuffix(toAppendTo); 237 if (currencyUnit.value != null) { 238 currencyUnit.value.writeSuffix(toAppendTo); 239 } 240 return toAppendTo; 241 } 242 243 /** 244 * {@inheritDoc} 245 */ 246 @Override 247 public AttributedCharacterIterator formatToCharacterIterator(Object obj) { 248 if (!(obj instanceof Number)) { 249 throw new IllegalArgumentException(); 250 } 251 Number number = (Number) obj; 252 Amount amount = toAmount(number.doubleValue(), null); 253 return super.formatToCharacterIterator(amount.getQty(), amount.getUnit()); 254 } 255 256 /** 257 * {@inheritDoc} 258 */ 259 @Override 260 public StringBuffer format(long number, StringBuffer toAppendTo, FieldPosition pos) { 261 return format((double) number, toAppendTo, pos); 262 } 263 264 /** 265 * {@inheritDoc} 266 */ 267 @Override 268 public StringBuffer format(BigInteger number, StringBuffer toAppendTo, FieldPosition pos) { 269 return format(number.doubleValue(), toAppendTo, pos); 270 } 271 272 /** 273 * {@inheritDoc} 274 */ 275 @Override 276 public StringBuffer format(BigDecimal number, StringBuffer toAppendTo, FieldPosition pos) { 277 return format(number.doubleValue(), toAppendTo, pos); 278 } 279 280 /** 281 * {@inheritDoc} 282 */ 283 @Override 284 public StringBuffer format(android.icu.math.BigDecimal number, StringBuffer toAppendTo, FieldPosition pos) { 285 return format(number.doubleValue(), toAppendTo, pos); 286 } 287 288 /** 289 * Parsing is currently unsupported, and throws an UnsupportedOperationException. 290 */ 291 @Override 292 public Number parse(String text, ParsePosition parsePosition) { 293 throw new UnsupportedOperationException(); 294 } 295 296 // DISALLOW Serialization, at least while draft 297 298 private void writeObject(ObjectOutputStream out) throws IOException { 299 throw new NotSerializableException(); 300 } 301 302 private void readObject(ObjectInputStream in) throws IOException { 303 throw new NotSerializableException(); 304 } 305 306 /* INTERNALS */ 307 308 309 private Amount toAmount(double number, Output<Unit> currencyUnit) { 310 // We do this here so that the prefix or suffix we choose is always consistent 311 // with the rounding we do. This way, 999999 -> 1M instead of 1000K. 312 boolean negative = isNumberNegative(number); 313 number = adjustNumberAsInFormatting(number); 314 int base = number <= 1.0d ? 0 : (int) Math.log10(number); 315 if (base >= CompactDecimalDataCache.MAX_DIGITS) { 316 base = CompactDecimalDataCache.MAX_DIGITS - 1; 317 } 318 number /= divisor[base]; 319 String pluralVariant = getPluralForm(getFixedDecimal(number, toDigitList(number))); 320 if (pluralToCurrencyAffixes != null && currencyUnit != null) { 321 currencyUnit.value = pluralToCurrencyAffixes.get(pluralVariant); 322 } 323 if (negative) { 324 number = -number; 325 } 326 return new Amount( 327 number, 328 CompactDecimalDataCache.getUnit(units, pluralVariant, base)); 329 330 } 331 332 private void recordError(Collection<String> creationErrors, String errorMessage) { 333 if (creationErrors == null) { 334 throw new IllegalArgumentException(errorMessage); 335 } 336 creationErrors.add(errorMessage); 337 } 338 339 /** 340 * Manufacture the unit list from arrays 341 */ 342 private Map<String, DecimalFormat.Unit[]> otherPluralVariant(Map<String, String[][]> pluralCategoryToPower10ToAffix, 343 long[] divisor, Collection<String> debugCreationErrors) { 344 345 // check for bad divisors 346 if (divisor.length < CompactDecimalDataCache.MAX_DIGITS) { 347 recordError(debugCreationErrors, "Must have at least " + CompactDecimalDataCache.MAX_DIGITS + " prefix items."); 348 } 349 long oldDivisor = 0; 350 for (int i = 0; i < divisor.length; ++i) { 351 352 // divisor must be a power of 10, and must be less than or equal to 10^i 353 int log = (int) Math.log10(divisor[i]); 354 if (log > i) { 355 recordError(debugCreationErrors, "Divisor[" + i + "] must be less than or equal to 10^" + i 356 + ", but is: " + divisor[i]); 357 } 358 long roundTrip = (long) Math.pow(10.0d, log); 359 if (roundTrip != divisor[i]) { 360 recordError(debugCreationErrors, "Divisor[" + i + "] must be a power of 10, but is: " + divisor[i]); 361 } 362 363 if (divisor[i] < oldDivisor) { 364 recordError(debugCreationErrors, "Bad divisor, the divisor for 10E" + i + "(" + divisor[i] 365 + ") is less than the divisor for the divisor for 10E" + (i - 1) + "(" + oldDivisor + ")"); 366 } 367 oldDivisor = divisor[i]; 368 } 369 370 Map<String, DecimalFormat.Unit[]> result = new HashMap<String, DecimalFormat.Unit[]>(); 371 Map<String,Integer> seen = new HashMap<String,Integer>(); 372 373 String[][] defaultPower10ToAffix = pluralCategoryToPower10ToAffix.get("other"); 374 375 for (Entry<String, String[][]> pluralCategoryAndPower10ToAffix : pluralCategoryToPower10ToAffix.entrySet()) { 376 String pluralCategory = pluralCategoryAndPower10ToAffix.getKey(); 377 String[][] power10ToAffix = pluralCategoryAndPower10ToAffix.getValue(); 378 379 // we can't have one of the arrays be of different length 380 if (power10ToAffix.length != divisor.length) { 381 recordError(debugCreationErrors, "Prefixes & suffixes must be present for all divisors " + pluralCategory); 382 } 383 DecimalFormat.Unit[] units = new DecimalFormat.Unit[power10ToAffix.length]; 384 for (int i = 0; i < power10ToAffix.length; i++) { 385 String[] pair = power10ToAffix[i]; 386 if (pair == null) { 387 pair = defaultPower10ToAffix[i]; 388 } 389 390 // we can't have bad pair 391 if (pair.length != 2 || pair[0] == null || pair[1] == null) { 392 recordError(debugCreationErrors, "Prefix or suffix is null for " + pluralCategory + ", " + i + ", " + Arrays.asList(pair)); 393 continue; 394 } 395 396 // we can't have two different indexes with the same display 397 int log = (int) Math.log10(divisor[i]); 398 String key = pair[0] + "\uFFFF" + pair[1] + "\uFFFF" + (i - log); 399 Integer old = seen.get(key); 400 if (old == null) { 401 seen.put(key, i); 402 } else if (old != i) { 403 recordError(debugCreationErrors, "Collision between values for " + i + " and " + old 404 + " for [prefix/suffix/index-log(divisor)" + key.replace('\uFFFF', ';')); 405 } 406 407 units[i] = new Unit(pair[0], pair[1]); 408 } 409 result.put(pluralCategory, units); 410 } 411 return result; 412 } 413 414 private String getPluralForm(FixedDecimal fixedDecimal) { 415 if (pluralRules == null) { 416 return CompactDecimalDataCache.OTHER; 417 } 418 return pluralRules.select(fixedDecimal); 419 } 420 421 /** 422 * Gets the data for a particular locale and style. If style is unrecognized, 423 * we just return data for CompactStyle.SHORT. 424 * @param locale The locale. 425 * @param style The style. 426 * @return The data which must not be modified. 427 */ 428 private Data getData(ULocale locale, CompactStyle style) { 429 CompactDecimalDataCache.DataBundle bundle = cache.get(locale); 430 switch (style) { 431 case SHORT: 432 return bundle.shortData; 433 case LONG: 434 return bundle.longData; 435 default: 436 return bundle.shortData; 437 } 438 } 439 440 private static class Amount { 441 private final double qty; 442 private final Unit unit; 443 444 public Amount(double qty, Unit unit) { 445 this.qty = qty; 446 this.unit = unit; 447 } 448 449 public double getQty() { 450 return qty; 451 } 452 453 public Unit getUnit() { 454 return unit; 455 } 456 } 457} 458