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