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) 2008-2015, Google, International Business Machines Corporation and
6 * others. All Rights Reserved.
7 *******************************************************************************
8 */
9package com.ibm.icu.text;
10
11import java.util.Arrays;
12import java.util.EnumSet;
13
14import com.ibm.icu.impl.StandardPlural;
15import com.ibm.icu.util.Freezable;
16import com.ibm.icu.util.Output;
17
18/**
19 * Utility class for returning the plural category for a range of numbers, such as 1–5, so that appropriate messages can
20 * be chosen. The rules for determining this value vary widely across locales.
21 *
22 * @author markdavis
23 * @internal
24 * @deprecated This API is ICU internal only.
25 */
26@Deprecated
27public final class PluralRanges implements Freezable<PluralRanges>, Comparable<PluralRanges> {
28
29    private volatile boolean isFrozen;
30    private Matrix matrix = new Matrix();
31    private boolean[] explicit = new boolean[StandardPlural.COUNT];
32
33    /**
34     * Constructor
35     *
36     * @internal
37     * @deprecated This API is ICU internal only.
38     */
39    @Deprecated
40    public PluralRanges() {
41    }
42
43    /**
44     * Internal class for mapping from two StandardPluralCategories values to another.
45     */
46    private static final class Matrix implements Comparable<Matrix>, Cloneable {
47        private byte[] data = new byte[StandardPlural.COUNT * StandardPlural.COUNT];
48        {
49            for (int i = 0; i < data.length; ++i) {
50                data[i] = -1;
51            }
52        }
53
54        Matrix() {
55        }
56
57        /**
58         * Internal method for setting.
59         */
60        @SuppressWarnings("unused")
61        void set(StandardPlural start, StandardPlural end, StandardPlural result) {
62            data[start.ordinal() * StandardPlural.COUNT + end.ordinal()] = result == null ? (byte) -1
63                    : (byte) result.ordinal();
64        }
65
66        /**
67         * Internal method for setting; throws exception if already set.
68         */
69        void setIfNew(StandardPlural start, StandardPlural end,
70                StandardPlural result) {
71            byte old = data[start.ordinal() * StandardPlural.COUNT + end.ordinal()];
72            if (old >= 0) {
73                throw new IllegalArgumentException("Previously set value for <" + start + ", " + end + ", "
74                        + StandardPlural.VALUES.get(old) + ">");
75            }
76            data[start.ordinal() * StandardPlural.COUNT + end.ordinal()] = result == null ? (byte) -1
77                    : (byte) result.ordinal();
78        }
79
80        /**
81         * Internal method for getting.
82         */
83        StandardPlural get(StandardPlural start, StandardPlural end) {
84            byte result = data[start.ordinal() * StandardPlural.COUNT + end.ordinal()];
85            return result < 0 ? null : StandardPlural.VALUES.get(result);
86        }
87
88        /**
89         * Internal method to see if <*,end> values are all the same.
90         */
91        @SuppressWarnings("unused")
92        StandardPlural endSame(StandardPlural end) {
93            StandardPlural first = null;
94            for (StandardPlural start : StandardPlural.VALUES) {
95                StandardPlural item = get(start, end);
96                if (item == null) {
97                    continue;
98                }
99                if (first == null) {
100                    first = item;
101                    continue;
102                }
103                if (first != item) {
104                    return null;
105                }
106            }
107            return first;
108        }
109
110        /**
111         * Internal method to see if <start,*> values are all the same.
112         */
113        @SuppressWarnings("unused")
114        StandardPlural startSame(StandardPlural start,
115                EnumSet<StandardPlural> endDone, Output<Boolean> emit) {
116            emit.value = false;
117            StandardPlural first = null;
118            for (StandardPlural end : StandardPlural.VALUES) {
119                StandardPlural item = get(start, end);
120                if (item == null) {
121                    continue;
122                }
123                if (first == null) {
124                    first = item;
125                    continue;
126                }
127                if (first != item) {
128                    return null;
129                }
130                // only emit if we didn't cover with the 'end' values
131                if (!endDone.contains(end)) {
132                    emit.value = true;
133                }
134            }
135            return first;
136        }
137
138        @Override
139        public int hashCode() {
140            int result = 0;
141            for (int i = 0; i < data.length; ++i) {
142                result = result * 37 + data[i];
143            }
144            return result;
145        }
146
147        @Override
148        public boolean equals(Object other) {
149            if (!(other instanceof Matrix)) {
150                return false;
151            }
152            return 0 == compareTo((Matrix) other);
153        }
154
155        @Override
156        public int compareTo(Matrix o) {
157            for (int i = 0; i < data.length; ++i) {
158                int diff = data[i] - o.data[i];
159                if (diff != 0) {
160                    return diff;
161                }
162            }
163            return 0;
164        }
165
166        @Override
167        public Matrix clone() {
168            Matrix result = new Matrix();
169            result.data = data.clone();
170            return result;
171        }
172
173        @Override
174        public String toString() {
175            StringBuilder result = new StringBuilder();
176            for (StandardPlural i : StandardPlural.values()) {
177                for (StandardPlural j : StandardPlural.values()) {
178                    StandardPlural x = get(i, j);
179                    if (x != null) {
180                        result.append(i + " & " + j + " → " + x + ";\n");
181                    }
182                }
183            }
184            return result.toString();
185        }
186    }
187
188    /**
189     * Internal method for building. If the start or end are null, it means everything of that type.
190     *
191     * @param rangeStart
192     *            plural category for the start of the range
193     * @param rangeEnd
194     *            plural category for the end of the range
195     * @param result
196     *            the resulting plural category
197     * @internal
198     * @deprecated This API is ICU internal only.
199     */
200    @Deprecated
201    public void add(StandardPlural rangeStart, StandardPlural rangeEnd,
202            StandardPlural result) {
203        if (isFrozen) {
204            throw new UnsupportedOperationException();
205        }
206        explicit[result.ordinal()] = true;
207        if (rangeStart == null) {
208            for (StandardPlural rs : StandardPlural.values()) {
209                if (rangeEnd == null) {
210                    for (StandardPlural re : StandardPlural.values()) {
211                        matrix.setIfNew(rs, re, result);
212                    }
213                } else {
214                    explicit[rangeEnd.ordinal()] = true;
215                    matrix.setIfNew(rs, rangeEnd, result);
216                }
217            }
218        } else if (rangeEnd == null) {
219            explicit[rangeStart.ordinal()] = true;
220            for (StandardPlural re : StandardPlural.values()) {
221                matrix.setIfNew(rangeStart, re, result);
222            }
223        } else {
224            explicit[rangeStart.ordinal()] = true;
225            explicit[rangeEnd.ordinal()] = true;
226            matrix.setIfNew(rangeStart, rangeEnd, result);
227        }
228    }
229
230    /**
231     * Returns the appropriate plural category for a range from start to end. If there is no available data, then
232     * 'end' is returned as an implicit value. (Such an implicit value can be tested for with {@link #isExplicit}.)
233     *
234     * @param start
235     *            plural category for the start of the range
236     * @param end
237     *            plural category for the end of the range
238     * @return the resulting plural category, or 'end' if there is no data.
239     * @internal
240     * @deprecated This API is ICU internal only.
241     */
242    @Deprecated
243    public StandardPlural get(StandardPlural start, StandardPlural end) {
244        StandardPlural result = matrix.get(start, end);
245        return result == null ? end : result;
246    }
247
248    /**
249     * Returns whether the appropriate plural category for a range from start to end
250     * is explicitly in the data (vs given an implicit value). See also {@link #get}.
251     *
252     * @param start
253     *            plural category for the start of the range
254     * @param end
255     *            plural category for the end of the range
256     * @return whether the value for (start,end) is explicit or not.
257     * @internal
258     * @deprecated This API is ICU internal only.
259     */
260    @Deprecated
261    public boolean isExplicit(StandardPlural start, StandardPlural end) {
262        return matrix.get(start, end) != null;
263    }
264
265    /**
266     * Internal method to determines whether the StandardPluralCategories was explicitly used in any add statement.
267     *
268     * @param count
269     *            plural category to test
270     * @return true if set
271     * @internal
272     * @deprecated This API is ICU internal only.
273     */
274    @Deprecated
275    public boolean isExplicitlySet(StandardPlural count) {
276        return explicit[count.ordinal()];
277    }
278
279    /**
280     * {@inheritDoc}
281     * @internal
282     * @deprecated This API is ICU internal only.
283     */
284    @Deprecated
285    @Override
286    public boolean equals(Object other) {
287        if (this == other) {
288            return true;
289        }
290        if (!(other instanceof PluralRanges)) {
291            return false;
292        }
293        PluralRanges otherPR = (PluralRanges)other;
294        return matrix.equals(otherPR.matrix) && Arrays.equals(explicit, otherPR.explicit);
295    }
296
297    /**
298     * {@inheritDoc}
299     * @internal
300     * @deprecated This API is ICU internal only.
301     */
302    @Override
303    @Deprecated
304    public int hashCode() {
305        return matrix.hashCode();
306    }
307
308    /**
309     * {@inheritDoc}
310     * @internal
311     * @deprecated This API is ICU internal only.
312     */
313    @Override
314    @Deprecated
315    public int compareTo(PluralRanges that) {
316        return matrix.compareTo(that.matrix);
317    }
318
319    /**
320     * {@inheritDoc}
321     * @internal
322     * @deprecated This API is ICU internal only.
323     */
324    @Override
325    @Deprecated
326    public boolean isFrozen() {
327        return isFrozen;
328    }
329
330    /**
331     * {@inheritDoc}
332     * @internal
333     * @deprecated This API is ICU internal only.
334     */
335    @Override
336    @Deprecated
337    public PluralRanges freeze() {
338        isFrozen = true;
339        return this;
340    }
341
342    /**
343     * {@inheritDoc}
344     * @internal
345     * @deprecated This API is ICU internal only.
346     */
347    @Override
348    @Deprecated
349    public PluralRanges cloneAsThawed() {
350        PluralRanges result = new PluralRanges();
351        result.explicit = explicit.clone();
352        result.matrix = matrix.clone();
353        return result;
354    }
355
356    /**
357     * {@inheritDoc}
358     * @internal
359     * @deprecated This API is ICU internal only.
360     */
361    @Override
362    @Deprecated
363    public String toString() {
364        return matrix.toString();
365    }
366}