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