1/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements.  See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package java.text;
19
20import java.util.ArrayList;
21import java.util.Arrays;
22import java.util.List;
23import java.util.Locale;
24
25/**
26 * Returns a fixed string based on a numeric value. The class can be used in
27 * conjunction with the {@link MessageFormat} class to handle plurals in
28 * messages. {@code ChoiceFormat} enables users to attach a format to a range of
29 * numbers. The choice is specified with an ascending list of doubles, where
30 * each item specifies a half-open interval up to the next item as in the
31 * following: X matches j if and only if {@code limit[j] <= X < limit[j+1]}.
32 * <p>
33 * If there is no match, then either the first or last index is used. The first
34 * or last index is used depending on whether the number is too low or too high.
35 * The length of the format array must be the same as the length of the limits
36 * array.
37 * <h5>Examples:</h5>
38 * <blockquote>
39 *
40 * <pre>
41 * double[] limits = {1, 2, 3, 4, 5, 6, 7};
42 * String[] fmts = {"Sun", "Mon", "Tue", "Wed", "Thur", "Fri", "Sat"};
43 *
44 * double[] limits2 = {0, 1, ChoiceFormat.nextDouble(1)};
45 * String[] fmts2 = {"no files", "one file", "many files"};
46 * </pre>
47 * </blockquote>
48 * <p>
49 * ChoiceFormat.nextDouble(double) allows to get the double following the one
50 * passed to the method. This is used to create half open intervals.
51 * <p>
52 * {@code ChoiceFormat} objects also may be converted to and from patterns.
53 * The conversion can be done programmatically, as in the example above, or
54 * by using a pattern like the following:
55 * <blockquote>
56 *
57 * <pre>
58 * "1#Sun|2#Mon|3#Tue|4#Wed|5#Thur|6#Fri|7#Sat"
59 * "0#are no files|1#is one file|1&lt;are many files"
60 * </pre>
61 *
62 * </blockquote>
63 * <p>
64 * where:
65 * <ul>
66 * <li><number>"#"</number> specifies an inclusive limit value;</li>
67 * <li><number>"<"</number> specifies an exclusive limit value.</li>
68 * </ul>
69 */
70public class ChoiceFormat extends NumberFormat {
71
72    private static final long serialVersionUID = 1795184449645032964L;
73
74    private double[] choiceLimits;
75
76    private String[] choiceFormats;
77
78    /**
79     * Constructs a new {@code ChoiceFormat} with the specified double values
80     * and associated strings. When calling
81     * {@link #format(double, StringBuffer, FieldPosition) format} with a double
82     * value {@code d}, then the element {@code i} in {@code formats} is
83     * selected where {@code i} fulfills {@code limits[i] <= d < limits[i+1]}.
84     * <p>
85     * The length of the {@code limits} and {@code formats} arrays must be the
86     * same.
87     *
88     * @param limits
89     *            an array of doubles in ascending order. The lowest and highest
90     *            possible values are negative and positive infinity.
91     * @param formats
92     *            the strings associated with the ranges defined through {@code
93     *            limits}. The lower bound of the associated range is at the
94     *            same index as the string.
95     */
96    public ChoiceFormat(double[] limits, String[] formats) {
97        setChoices(limits, formats);
98    }
99
100    /**
101     * Constructs a new {@code ChoiceFormat} with the strings and limits parsed
102     * from the specified pattern.
103     *
104     * @param template
105     *            the pattern of strings and ranges.
106     * @throws IllegalArgumentException
107     *            if an error occurs while parsing the pattern.
108     */
109    public ChoiceFormat(String template) {
110        applyPattern(template);
111    }
112
113    /**
114     * Parses the pattern to determine new strings and ranges for this
115     * {@code ChoiceFormat}.
116     *
117     * @param template
118     *            the pattern of strings and ranges.
119     * @throws IllegalArgumentException
120     *            if an error occurs while parsing the pattern.
121     */
122    public void applyPattern(String template) {
123        double[] limits = new double[5];
124        List<String> formats = new ArrayList<String>();
125        int length = template.length(), limitCount = 0, index = 0;
126        StringBuffer buffer = new StringBuffer();
127        NumberFormat format = NumberFormat.getInstance(Locale.US);
128        ParsePosition position = new ParsePosition(0);
129        while (true) {
130            index = skipWhitespace(template, index);
131            if (index >= length) {
132                if (limitCount == limits.length) {
133                    choiceLimits = limits;
134                } else {
135                    choiceLimits = new double[limitCount];
136                    System.arraycopy(limits, 0, choiceLimits, 0, limitCount);
137                }
138                choiceFormats = new String[formats.size()];
139                for (int i = 0; i < formats.size(); i++) {
140                    choiceFormats[i] = formats.get(i);
141                }
142                return;
143            }
144
145            position.setIndex(index);
146            Number value = format.parse(template, position);
147            index = skipWhitespace(template, position.getIndex());
148            if (position.getErrorIndex() != -1 || index >= length) {
149                // Fix Harmony 540
150                choiceLimits = new double[0];
151                choiceFormats = new String[0];
152                return;
153            }
154            char ch = template.charAt(index++);
155            if (limitCount == limits.length) {
156                double[] newLimits = new double[limitCount * 2];
157                System.arraycopy(limits, 0, newLimits, 0, limitCount);
158                limits = newLimits;
159            }
160            double next;
161            switch (ch) {
162                case '#':
163                case '\u2264':
164                    next = value.doubleValue();
165                    break;
166                case '<':
167                    next = nextDouble(value.doubleValue());
168                    break;
169                default:
170                    throw new IllegalArgumentException();
171            }
172            if (limitCount > 0 && next <= limits[limitCount - 1]) {
173                throw new IllegalArgumentException();
174            }
175            buffer.setLength(0);
176            position.setIndex(index);
177            upTo(template, position, buffer, '|');
178            index = position.getIndex();
179            limits[limitCount++] = next;
180            formats.add(buffer.toString());
181        }
182    }
183
184    /**
185     * Returns a new instance of {@code ChoiceFormat} with the same ranges and
186     * strings as this {@code ChoiceFormat}.
187     *
188     * @return a shallow copy of this {@code ChoiceFormat}.
189     *
190     * @see java.lang.Cloneable
191     */
192    @Override
193    public Object clone() {
194        ChoiceFormat clone = (ChoiceFormat) super.clone();
195        clone.choiceLimits = choiceLimits.clone();
196        clone.choiceFormats = choiceFormats.clone();
197        return clone;
198    }
199
200    /**
201     * Compares the specified object with this {@code ChoiceFormat}. The object
202     * must be an instance of {@code ChoiceFormat} and have the same limits and
203     * formats to be equal to this instance.
204     *
205     * @param object
206     *            the object to compare with this instance.
207     * @return {@code true} if the specified object is equal to this instance;
208     *         {@code false} otherwise.
209     * @see #hashCode
210     */
211    @Override
212    public boolean equals(Object object) {
213        if (this == object) {
214            return true;
215        }
216        if (!(object instanceof ChoiceFormat)) {
217            return false;
218        }
219        ChoiceFormat choice = (ChoiceFormat) object;
220        return Arrays.equals(choiceLimits, choice.choiceLimits)
221                && Arrays.equals(choiceFormats, choice.choiceFormats);
222    }
223
224    /**
225     * Appends the string associated with the range in which the specified
226     * double value fits to the specified string buffer.
227     *
228     * @param value
229     *            the double to format.
230     * @param buffer
231     *            the target string buffer to append the formatted value to.
232     * @param field
233     *            a {@code FieldPosition} which is ignored.
234     * @return the string buffer.
235     */
236    @Override
237    public StringBuffer format(double value, StringBuffer buffer,
238            FieldPosition field) {
239        for (int i = choiceLimits.length - 1; i >= 0; i--) {
240            if (choiceLimits[i] <= value) {
241                return buffer.append(choiceFormats[i]);
242            }
243        }
244        return choiceFormats.length == 0 ? buffer : buffer
245                .append(choiceFormats[0]);
246    }
247
248    /**
249     * Appends the string associated with the range in which the specified long
250     * value fits to the specified string buffer.
251     *
252     * @param value
253     *            the long to format.
254     * @param buffer
255     *            the target string buffer to append the formatted value to.
256     * @param field
257     *            a {@code FieldPosition} which is ignored.
258     * @return the string buffer.
259     */
260    @Override
261    public StringBuffer format(long value, StringBuffer buffer,
262            FieldPosition field) {
263        return format((double) value, buffer, field);
264    }
265
266    /**
267     * Returns the strings associated with the ranges of this {@code
268     * ChoiceFormat}.
269     *
270     * @return an array of format strings.
271     */
272    public Object[] getFormats() {
273        return choiceFormats;
274    }
275
276    /**
277     * Returns the limits of this {@code ChoiceFormat}.
278     *
279     * @return the array of doubles which make up the limits of this {@code
280     *         ChoiceFormat}.
281     */
282    public double[] getLimits() {
283        return choiceLimits;
284    }
285
286    /**
287     * Returns an integer hash code for the receiver. Objects which are equal
288     * return the same value for this method.
289     *
290     * @return the receiver's hash.
291     *
292     * @see #equals
293     */
294    @Override
295    public int hashCode() {
296        int hashCode = 0;
297        for (int i = 0; i < choiceLimits.length; i++) {
298            long v = Double.doubleToLongBits(choiceLimits[i]);
299            hashCode += (int) (v ^ (v >>> 32)) + choiceFormats[i].hashCode();
300        }
301        return hashCode;
302    }
303
304    /**
305     * Returns the double value which is closest to the specified double but
306     * larger.
307     *
308     * @param value
309     *            a double value.
310     * @return the next larger double value.
311     */
312    public static final double nextDouble(double value) {
313        if (value == Double.POSITIVE_INFINITY) {
314            return value;
315        }
316        long bits;
317        // Handle -0.0
318        if (value == 0) {
319            bits = 0;
320        } else {
321            bits = Double.doubleToLongBits(value);
322        }
323        return Double.longBitsToDouble(value < 0 ? bits - 1 : bits + 1);
324    }
325
326    /**
327     * Returns the double value which is closest to the specified double but
328     * either larger or smaller as specified.
329     *
330     * @param value
331     *            a double value.
332     * @param increment
333     *            {@code true} to get the next larger value, {@code false} to
334     *            get the previous smaller value.
335     * @return the next larger or smaller double value.
336     */
337    public static double nextDouble(double value, boolean increment) {
338        return increment ? nextDouble(value) : previousDouble(value);
339    }
340
341    /**
342     * Parses a double from the specified string starting at the index specified
343     * by {@code position}. The string is compared to the strings of this
344     * {@code ChoiceFormat} and if a match occurs then the lower bound of the
345     * corresponding range in the limits array is returned. If the string is
346     * successfully parsed then the index of the {@code ParsePosition} passed to
347     * this method is updated to the index following the parsed text.
348     * <p>
349     * If one of the format strings of this {@code ChoiceFormat} instance is
350     * found in {@code string} starting at {@code position.getIndex()} then
351     * <ul>
352     * <li>the index in {@code position} is set to the index following the
353     * parsed text;
354     * <li>the {@link java.lang.Double Double} corresponding to the format
355     * string is returned.</li>
356     * </ul>
357     * <p>
358     * If none of the format strings is found in {@code string} then
359     * <ul>
360     * <li>the error index in {@code position} is set to the current index in
361     * {@code position};</li>
362     * <li> {@link java.lang.Double#NaN Double.NaN} is returned.
363     * </ul>
364     * @param string
365     *            the source string to parse.
366     * @param position
367     *            input/output parameter, specifies the start index in {@code
368     *            string} from where to start parsing. See the <em>Returns</em>
369     *            section for a description of the output values.
370     * @return a Double resulting from the parse, or Double.NaN if there is an
371     *         error
372     */
373    @Override
374    public Number parse(String string, ParsePosition position) {
375        int offset = position.getIndex();
376        for (int i = 0; i < choiceFormats.length; i++) {
377            if (string.startsWith(choiceFormats[i], offset)) {
378                position.setIndex(offset + choiceFormats[i].length());
379                return new Double(choiceLimits[i]);
380            }
381        }
382        position.setErrorIndex(offset);
383        return new Double(Double.NaN);
384    }
385
386    /**
387     * Returns the double value which is closest to the specified double but
388     * smaller.
389     *
390     * @param value
391     *            a double value.
392     * @return the next smaller double value.
393     */
394    public static final double previousDouble(double value) {
395        if (value == Double.NEGATIVE_INFINITY) {
396            return value;
397        }
398        long bits;
399        // Handle 0.0
400        if (value == 0) {
401            bits = 0x8000000000000000L;
402        } else {
403            bits = Double.doubleToLongBits(value);
404        }
405        return Double.longBitsToDouble(value <= 0 ? bits + 1 : bits - 1);
406    }
407
408    /**
409     * Sets the double values and associated strings of this ChoiceFormat. When
410     * calling {@link #format(double, StringBuffer, FieldPosition) format} with
411     * a double value {@code d}, then the element {@code i} in {@code formats}
412     * is selected where {@code i} fulfills
413     * {@code limits[i] <= d < limits[i+1]}.
414     * <p>
415     * The length of the {@code limits} and {@code formats} arrays must be the
416     * same.
417     *
418     * @param limits
419     *            an array of doubles in ascending order. The lowest and highest
420     *            possible values are negative and positive infinity.
421     * @param formats
422     *            the strings associated with the ranges defined through {@code
423     *            limits}. The lower bound of the associated range is at the
424     *            same index as the string.
425     */
426    public void setChoices(double[] limits, String[] formats) {
427        if (limits.length != formats.length) {
428            throw new IllegalArgumentException();
429        }
430        choiceLimits = limits;
431        choiceFormats = formats;
432    }
433
434    private int skipWhitespace(String string, int index) {
435        int length = string.length();
436        while (index < length && Character.isWhitespace(string.charAt(index))) {
437            index++;
438        }
439        return index;
440    }
441
442    /**
443     * Returns the pattern of this {@code ChoiceFormat} which specifies the
444     * ranges and their associated strings.
445     *
446     * @return the pattern.
447     */
448    public String toPattern() {
449        StringBuilder buffer = new StringBuilder();
450        for (int i = 0; i < choiceLimits.length; i++) {
451            if (i != 0) {
452                buffer.append('|');
453            }
454            String previous = String.valueOf(previousDouble(choiceLimits[i]));
455            String limit = String.valueOf(choiceLimits[i]);
456            if (previous.length() < limit.length()) {
457                buffer.append(previous);
458                buffer.append('<');
459            } else {
460                buffer.append(limit);
461                buffer.append('#');
462            }
463            boolean quote = (choiceFormats[i].indexOf('|') != -1);
464            if (quote) {
465                buffer.append('\'');
466            }
467            buffer.append(choiceFormats[i]);
468            if (quote) {
469                buffer.append('\'');
470            }
471        }
472        return buffer.toString();
473    }
474}
475