1fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert// © 2017 and later: Unicode, Inc. and others.
2fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert// License & terms of use: http://www.unicode.org/copyright.html#License
3fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubertpackage com.ibm.icu.impl.number;
4fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
5fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubertimport java.util.Arrays;
6fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubertimport java.util.Map;
7fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubertimport java.util.Set;
8fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
9fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubertimport com.ibm.icu.impl.ICUData;
10fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubertimport com.ibm.icu.impl.ICUResourceBundle;
11fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubertimport com.ibm.icu.impl.StandardPlural;
12fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubertimport com.ibm.icu.impl.UResource;
13fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubertimport com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
14fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubertimport com.ibm.icu.util.ICUException;
15fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubertimport com.ibm.icu.util.ULocale;
16fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubertimport com.ibm.icu.util.UResourceBundle;
17fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
18fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubertpublic class CompactData implements MultiplierProducer {
19fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
20fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    public enum CompactType {
21fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        DECIMAL, CURRENCY
22fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    }
23fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
24fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    // A dummy object used when a "0" compact decimal entry is encountered. This is necessary
25fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    // in order to prevent falling back to root. Object equality ("==") is intended.
26fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    private static final String USE_FALLBACK = "<USE FALLBACK>";
27fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
28fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    private final String[] patterns;
29fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    private final byte[] multipliers;
30fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    private byte largestMagnitude;
31fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    private boolean isEmpty;
32fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
33fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    private static final int COMPACT_MAX_DIGITS = 15;
34fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
35fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    public CompactData() {
36fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        patterns = new String[(CompactData.COMPACT_MAX_DIGITS + 1) * StandardPlural.COUNT];
37fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        multipliers = new byte[CompactData.COMPACT_MAX_DIGITS + 1];
38fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        largestMagnitude = 0;
39fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        isEmpty = true;
40fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    }
41fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
42fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    public void populate(ULocale locale, String nsName, CompactStyle compactStyle, CompactType compactType) {
43fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        assert isEmpty;
44fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        CompactDataSink sink = new CompactDataSink(this);
45fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, locale);
46fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
47fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        boolean nsIsLatn = nsName.equals("latn");
48fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        boolean compactIsShort = compactStyle == CompactStyle.SHORT;
49fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
50fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        // Fall back to latn numbering system and/or short compact style.
51fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        StringBuilder resourceKey = new StringBuilder();
52fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        getResourceBundleKey(nsName, compactStyle, compactType, resourceKey);
53fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        rb.getAllItemsWithFallbackNoFail(resourceKey.toString(), sink);
54fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        if (isEmpty && !nsIsLatn) {
55fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            getResourceBundleKey("latn", compactStyle, compactType, resourceKey);
56fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            rb.getAllItemsWithFallbackNoFail(resourceKey.toString(), sink);
57fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
58fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        if (isEmpty && !compactIsShort) {
59fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            getResourceBundleKey(nsName, CompactStyle.SHORT, compactType, resourceKey);
60fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            rb.getAllItemsWithFallbackNoFail(resourceKey.toString(), sink);
61fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
62fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        if (isEmpty && !nsIsLatn && !compactIsShort) {
63fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            getResourceBundleKey("latn", CompactStyle.SHORT, compactType, resourceKey);
64fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            rb.getAllItemsWithFallbackNoFail(resourceKey.toString(), sink);
65fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
66fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
67fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        // The last fallback should be guaranteed to return data.
68fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        if (isEmpty) {
69fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            throw new ICUException("Could not load compact decimal data for locale " + locale);
70fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
71fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    }
72fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
73fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    /** Produces a string like "NumberElements/latn/patternsShort/decimalFormat". */
74fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    private static void getResourceBundleKey(String nsName, CompactStyle compactStyle, CompactType compactType, StringBuilder sb) {
75fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        sb.setLength(0);
76fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        sb.append("NumberElements/");
77fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        sb.append(nsName);
78fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        sb.append(compactStyle == CompactStyle.SHORT ? "/patternsShort" : "/patternsLong");
79fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        sb.append(compactType == CompactType.DECIMAL ? "/decimalFormat" : "/currencyFormat");
80fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    }
81fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
82fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    /** Java-only method used by CLDR tooling. */
83fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    public void populate(Map<String, Map<String, String>> powersToPluralsToPatterns) {
84fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        assert isEmpty;
85fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        for (Map.Entry<String, Map<String, String>> magnitudeEntry : powersToPluralsToPatterns.entrySet()) {
86fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            byte magnitude = (byte) (magnitudeEntry.getKey().length() - 1);
87fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            for (Map.Entry<String, String> pluralEntry : magnitudeEntry.getValue().entrySet()) {
88fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                StandardPlural plural = StandardPlural.fromString(pluralEntry.getKey().toString());
89fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                String patternString = pluralEntry.getValue().toString();
90fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                patterns[getIndex(magnitude, plural)] = patternString;
91fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                int numZeros = countZeros(patternString);
92fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                if (numZeros > 0) { // numZeros==0 in certain cases, like Somali "Kun"
93fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    // Save the multiplier.
94fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    multipliers[magnitude] = (byte) (numZeros - magnitude - 1);
95fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    if (magnitude > largestMagnitude) {
96fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                        largestMagnitude = magnitude;
97fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    }
98fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    isEmpty = false;
99fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                }
100fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            }
101fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
102fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    }
103fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
104fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    @Override
105fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    public int getMultiplier(int magnitude) {
106fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        if (magnitude < 0) {
107fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            return 0;
108fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
109fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        if (magnitude > largestMagnitude) {
110fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            magnitude = largestMagnitude;
111fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
112fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        return multipliers[magnitude];
113fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    }
114fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
115fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    public String getPattern(int magnitude, StandardPlural plural) {
116fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        if (magnitude < 0) {
117fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            return null;
118fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
119fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        if (magnitude > largestMagnitude) {
120fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            magnitude = largestMagnitude;
121fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
122fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        String patternString = patterns[getIndex(magnitude, plural)];
123fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        if (patternString == null && plural != StandardPlural.OTHER) {
124fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            // Fall back to "other" plural variant
125fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            patternString = patterns[getIndex(magnitude, StandardPlural.OTHER)];
126fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
127fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        if (patternString == USE_FALLBACK) { // == is intended
128fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            // Return null if USE_FALLBACK is present
129fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            patternString = null;
130fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
131fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        return patternString;
132fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    }
133fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
134fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    public void getUniquePatterns(Set<String> output) {
135fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        assert output.isEmpty();
136fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        // NOTE: In C++, this is done more manually with a UVector.
137fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        // In Java, we can take advantage of JDK HashSet.
138fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        output.addAll(Arrays.asList(patterns));
139fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        output.remove(USE_FALLBACK);
140fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        output.remove(null);
141fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    }
142fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
143fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    private static final class CompactDataSink extends UResource.Sink {
144fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
145fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        CompactData data;
146fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
147fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        public CompactDataSink(CompactData data) {
148fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            this.data = data;
149fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
150fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
151fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        @Override
152fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        public void put(UResource.Key key, UResource.Value value, boolean isRoot) {
153fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            // traverse into the table of powers of ten
154fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            UResource.Table powersOfTenTable = value.getTable();
155fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            for (int i3 = 0; powersOfTenTable.getKeyAndValue(i3, key, value); ++i3) {
156fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
157fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                // Assumes that the keys are always of the form "10000" where the magnitude is the
158fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                // length of the key minus one.  We expect magnitudes to be less than MAX_DIGITS.
159fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                byte magnitude = (byte) (key.length() - 1);
160fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                byte multiplier = data.multipliers[magnitude];
161fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                assert magnitude < COMPACT_MAX_DIGITS;
162fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
163fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                // Iterate over the plural variants ("one", "other", etc)
164fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                UResource.Table pluralVariantsTable = value.getTable();
165fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                for (int i4 = 0; pluralVariantsTable.getKeyAndValue(i4, key, value); ++i4) {
166fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
167fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    // Skip this magnitude/plural if we already have it from a child locale.
168fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    // Note: This also skips USE_FALLBACK entries.
169fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    StandardPlural plural = StandardPlural.fromString(key.toString());
170fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    if (data.patterns[getIndex(magnitude, plural)] != null) {
171fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                        continue;
172fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    }
173fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
174fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    // The value "0" means that we need to use the default pattern and not fall back
175fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    // to parent locales. Example locale where this is relevant: 'it'.
176fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    String patternString = value.toString();
177fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    if (patternString.equals("0")) {
178fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                        patternString = USE_FALLBACK;
179fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    }
180fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
181fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    // Save the pattern string. We will parse it lazily.
182fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    data.patterns[getIndex(magnitude, plural)] = patternString;
183fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
184fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    // If necessary, compute the multiplier: the difference between the magnitude
185fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    // and the number of zeros in the pattern.
186fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    if (multiplier == 0) {
187fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                        int numZeros = countZeros(patternString);
188fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                        if (numZeros > 0) { // numZeros==0 in certain cases, like Somali "Kun"
189fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                            multiplier = (byte) (numZeros - magnitude - 1);
190fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                        }
191fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    }
192fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                }
193fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
194fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                // Save the multiplier.
195fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                if (data.multipliers[magnitude] == 0) {
196fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    data.multipliers[magnitude] = multiplier;
197fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    if (magnitude > data.largestMagnitude) {
198fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                        data.largestMagnitude = magnitude;
199fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    }
200fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    data.isEmpty = false;
201fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                } else {
202fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                    assert data.multipliers[magnitude] == multiplier;
203fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                }
204fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            }
205fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
206fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    }
207fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
208fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    private static final int getIndex(int magnitude, StandardPlural plural) {
209fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        return magnitude * StandardPlural.COUNT + plural.ordinal();
210fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    }
211fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert
212fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    private static final int countZeros(String patternString) {
213fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        // NOTE: This strategy for computing the number of zeros is a hack for efficiency.
214fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        // It could break if there are any 0s that aren't part of the main pattern.
215fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        int numZeros = 0;
216fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        for (int i = 0; i < patternString.length(); i++) {
217fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            if (patternString.charAt(i) == '0') {
218fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                numZeros++;
219fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            } else if (numZeros > 0) {
220fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert                break; // zeros should always be contiguous
221fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert            }
222fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        }
223fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert        return numZeros;
224fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert    }
225fe77e7203e518f62b5bd8e8c603bca361e9cf47bFredrik Roubert}
226