KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > java > text > ChoiceFormat


1 /*
2  * @(#)ChoiceFormat.java 1.34 03/12/19
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7
8 /*
9  * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved
10  * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved
11  *
12  * The original version of this source code and documentation is copyrighted
13  * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These
14  * materials are provided under terms of a License Agreement between Taligent
15  * and Sun. This technology is protected by multiple US and International
16  * patents. This notice and attribution to Taligent may not be removed.
17  * Taligent is a registered trademark of Taligent, Inc.
18  *
19  */

20
21 package java.text;
22
23 import java.io.InvalidObjectException JavaDoc;
24 import java.io.IOException JavaDoc;
25 import java.io.ObjectInputStream JavaDoc;
26 import sun.text.Utility;
27
28 /**
29  * A <code>ChoiceFormat</code> allows you to attach a format to a range of numbers.
30  * It is generally used in a <code>MessageFormat</code> for handling plurals.
31  * The choice is specified with an ascending list of doubles, where each item
32  * specifies a half-open interval up to the next item:
33  * <blockquote>
34  * <pre>
35  * X matches j if and only if limit[j] &lt;= X &lt; limit[j+1]
36  * </pre>
37  * </blockquote>
38  * If there is no match, then either the first or last index is used, depending
39  * on whether the number (X) is too low or too high. If the limit array is not
40  * in ascending order, the results of formatting will be incorrect. ChoiceFormat
41  * also accepts <code>&#92;u221E</code> as equivalent to infinity(INF).
42  *
43  * <p>
44  * <strong>Note:</strong>
45  * <code>ChoiceFormat</code> differs from the other <code>Format</code>
46  * classes in that you create a <code>ChoiceFormat</code> object with a
47  * constructor (not with a <code>getInstance</code> style factory
48  * method). The factory methods aren't necessary because <code>ChoiceFormat</code>
49  * doesn't require any complex setup for a given locale. In fact,
50  * <code>ChoiceFormat</code> doesn't implement any locale specific behavior.
51  *
52  * <p>
53  * When creating a <code>ChoiceFormat</code>, you must specify an array of formats
54  * and an array of limits. The length of these arrays must be the same.
55  * For example,
56  * <ul>
57  * <li>
58  * <em>limits</em> = {1,2,3,4,5,6,7}<br>
59  * <em>formats</em> = {"Sun","Mon","Tue","Wed","Thur","Fri","Sat"}
60  * <li>
61  * <em>limits</em> = {0, 1, ChoiceFormat.nextDouble(1)}<br>
62  * <em>formats</em> = {"no files", "one file", "many files"}<br>
63  * (<code>nextDouble</code> can be used to get the next higher double, to
64  * make the half-open interval.)
65  * </ul>
66  *
67  * <p>
68  * Here is a simple example that shows formatting and parsing:
69  * <blockquote>
70  * <pre>
71  * double[] limits = {1,2,3,4,5,6,7};
72  * String[] monthNames = {"Sun","Mon","Tue","Wed","Thur","Fri","Sat"};
73  * ChoiceFormat form = new ChoiceFormat(limits, monthNames);
74  * ParsePosition status = new ParsePosition(0);
75  * for (double i = 0.0; i &lt;= 8.0; ++i) {
76  * status.setIndex(0);
77  * System.out.println(i + " -&gt; " + form.format(i) + " -&gt; "
78  * + form.parse(form.format(i),status));
79  * }
80  * </pre>
81  * </blockquote>
82  * Here is a more complex example, with a pattern format:
83  * <blockquote>
84  * <pre>
85  * double[] filelimits = {0,1,2};
86  * String[] filepart = {"are no files","is one file","are {2} files"};
87  * ChoiceFormat fileform = new ChoiceFormat(filelimits, filepart);
88  * Format[] testFormats = {fileform, null, NumberFormat.getInstance()};
89  * MessageFormat pattform = new MessageFormat("There {0} on {1}");
90  * pattform.setFormats(testFormats);
91  * Object[] testArgs = {null, "ADisk", null};
92  * for (int i = 0; i &lt; 4; ++i) {
93  * testArgs[0] = new Integer(i);
94  * testArgs[2] = testArgs[0];
95  * System.out.println(pattform.format(testArgs));
96  * }
97  * </pre>
98  * </blockquote>
99  * <p>
100  * Specifying a pattern for ChoiceFormat objects is fairly straightforward.
101  * For example:
102  * <blockquote>
103  * <pre>
104  * ChoiceFormat fmt = new ChoiceFormat(
105  * "-1#is negative| 0#is zero or fraction | 1#is one |1.0&lt;is 1+ |2#is two |2&lt;is more than 2.");
106  * System.out.println("Formatter Pattern : " + fmt.toPattern());
107  *
108  * System.out.println("Format with -INF : " + fmt.format(Double.NEGATIVE_INFINITY));
109  * System.out.println("Format with -1.0 : " + fmt.format(-1.0));
110  * System.out.println("Format with 0 : " + fmt.format(0));
111  * System.out.println("Format with 0.9 : " + fmt.format(0.9));
112  * System.out.println("Format with 1.0 : " + fmt.format(1));
113  * System.out.println("Format with 1.5 : " + fmt.format(1.5));
114  * System.out.println("Format with 2 : " + fmt.format(2));
115  * System.out.println("Format with 2.1 : " + fmt.format(2.1));
116  * System.out.println("Format with NaN : " + fmt.format(Double.NaN));
117  * System.out.println("Format with +INF : " + fmt.format(Double.POSITIVE_INFINITY));
118  * </pre>
119  * </blockquote>
120  * And the output result would be like the following:
121  * <pre>
122  * <blockquote>
123  * Format with -INF : is negative
124  * Format with -1.0 : is negative
125  * Format with 0 : is zero or fraction
126  * Format with 0.9 : is zero or fraction
127  * Format with 1.0 : is one
128  * Format with 1.5 : is 1+
129  * Format with 2 : is two
130  * Format with 2.1 : is more than 2.
131  * Format with NaN : is negative
132  * Format with +INF : is more than 2.
133  * </pre>
134  * </blockquote>
135  *
136  * <h4><a name="synchronization">Synchronization</a></h4>
137  *
138  * <p>
139  * Choice formats are not synchronized.
140  * It is recommended to create separate format instances for each thread.
141  * If multiple threads access a format concurrently, it must be synchronized
142  * externally.
143  *
144  *
145  * @see DecimalFormat
146  * @see MessageFormat
147  * @version 1.22 09/21/98
148  * @author Mark Davis
149  */

150 public class ChoiceFormat extends NumberFormat JavaDoc {
151
152     // Proclaim serial compatibility with 1.1 FCS
153
private static final long serialVersionUID = 1795184449645032964L;
154
155     /**
156      * Sets the pattern.
157      * @param newPattern See the class description.
158      */

159     public void applyPattern(String JavaDoc newPattern) {
160         StringBuffer JavaDoc[] segments = new StringBuffer JavaDoc[2];
161         for (int i = 0; i < segments.length; ++i) {
162             segments[i] = new StringBuffer JavaDoc();
163         }
164         double[] newChoiceLimits = new double[30];
165         String JavaDoc[] newChoiceFormats = new String JavaDoc[30];
166         int count = 0;
167         int part = 0;
168         double startValue = 0;
169         double oldStartValue = Double.NaN;
170         boolean inQuote = false;
171         for (int i = 0; i < newPattern.length(); ++i) {
172             char ch = newPattern.charAt(i);
173             if (ch=='\'') {
174                 // Check for "''" indicating a literal quote
175
if ((i+1)<newPattern.length() && newPattern.charAt(i+1)==ch) {
176                     segments[part].append(ch);
177                     ++i;
178                 } else {
179                     inQuote = !inQuote;
180                 }
181             } else if (inQuote) {
182                 segments[part].append(ch);
183             } else if (ch == '<' || ch == '#' || ch == '\u2264') {
184                 if (segments[0].equals("")) {
185                     throw new IllegalArgumentException JavaDoc();
186                 }
187                 try {
188                     String JavaDoc tempBuffer = segments[0].toString();
189                     if (tempBuffer.equals("\u221E")) {
190                         startValue = Double.POSITIVE_INFINITY;
191                     } else if (tempBuffer.equals("-\u221E")) {
192                         startValue = Double.NEGATIVE_INFINITY;
193                     } else {
194                         startValue = Double.valueOf(segments[0].toString()).doubleValue();
195                     }
196                 } catch (Exception JavaDoc e) {
197                     throw new IllegalArgumentException JavaDoc();
198                 }
199                 if (ch == '<' && startValue != Double.POSITIVE_INFINITY &&
200                         startValue != Double.NEGATIVE_INFINITY) {
201                     startValue = nextDouble(startValue);
202                 }
203                 if (startValue <= oldStartValue) {
204                     throw new IllegalArgumentException JavaDoc();
205                 }
206                 segments[0].setLength(0);
207                 part = 1;
208             } else if (ch == '|') {
209                 if (count == newChoiceLimits.length) {
210                     newChoiceLimits = doubleArraySize(newChoiceLimits);
211                     newChoiceFormats = doubleArraySize(newChoiceFormats);
212                 }
213                 newChoiceLimits[count] = startValue;
214                 newChoiceFormats[count] = segments[1].toString();
215                 ++count;
216                 oldStartValue = startValue;
217                 segments[1].setLength(0);
218                 part = 0;
219             } else {
220                 segments[part].append(ch);
221             }
222         }
223         // clean up last one
224
if (part == 1) {
225             if (count == newChoiceLimits.length) {
226                 newChoiceLimits = doubleArraySize(newChoiceLimits);
227                 newChoiceFormats = doubleArraySize(newChoiceFormats);
228             }
229             newChoiceLimits[count] = startValue;
230             newChoiceFormats[count] = segments[1].toString();
231             ++count;
232         }
233         choiceLimits = new double[count];
234         System.arraycopy(newChoiceLimits, 0, choiceLimits, 0, count);
235         choiceFormats = new String JavaDoc[count];
236         System.arraycopy(newChoiceFormats, 0, choiceFormats, 0, count);
237     }
238
239     /**
240      * Gets the pattern.
241      */

242     public String JavaDoc toPattern() {
243         StringBuffer JavaDoc result = new StringBuffer JavaDoc();
244         for (int i = 0; i < choiceLimits.length; ++i) {
245             if (i != 0) {
246                 result.append('|');
247             }
248             // choose based upon which has less precision
249
// approximate that by choosing the closest one to an integer.
250
// could do better, but it's not worth it.
251
double less = previousDouble(choiceLimits[i]);
252             double tryLessOrEqual = Math.abs(Math.IEEEremainder(choiceLimits[i], 1.0d));
253             double tryLess = Math.abs(Math.IEEEremainder(less, 1.0d));
254
255             if (tryLessOrEqual < tryLess) {
256                 result.append(""+choiceLimits[i]);
257                 result.append('#');
258             } else {
259                 if (choiceLimits[i] == Double.POSITIVE_INFINITY) {
260                     result.append("\u221E");
261                 } else if (choiceLimits[i] == Double.NEGATIVE_INFINITY) {
262                     result.append("-\u221E");
263                 } else {
264                     result.append(""+less);
265                 }
266                 result.append('<');
267             }
268             // Append choiceFormats[i], using quotes if there are special characters.
269
// Single quotes themselves must be escaped in either case.
270
String JavaDoc text = choiceFormats[i];
271             boolean needQuote = text.indexOf('<') >= 0
272                 || text.indexOf('#') >= 0
273                 || text.indexOf('\u2264') >= 0
274                 || text.indexOf('|') >= 0;
275             if (needQuote) result.append('\'');
276             if (text.indexOf('\'') < 0) result.append(text);
277             else {
278                 for (int j=0; j<text.length(); ++j) {
279                     char c = text.charAt(j);
280                     result.append(c);
281                     if (c == '\'') result.append(c);
282                 }
283             }
284             if (needQuote) result.append('\'');
285         }
286         return result.toString();
287     }
288
289     /**
290      * Constructs with limits and corresponding formats based on the pattern.
291      * @see #applyPattern
292      */

293     public ChoiceFormat(String JavaDoc newPattern) {
294         applyPattern(newPattern);
295     }
296
297     /**
298      * Constructs with the limits and the corresponding formats.
299      * @see #setChoices
300      */

301     public ChoiceFormat(double[] limits, String JavaDoc[] formats) {
302         setChoices(limits, formats);
303     }
304
305     /**
306      * Set the choices to be used in formatting.
307      * @param limits contains the top value that you want
308      * parsed with that format,and should be in ascending sorted order. When
309      * formatting X, the choice will be the i, where
310      * limit[i] &lt;= X &lt; limit[i+1].
311      * If the limit array is not in ascending order, the results of formatting
312      * will be incorrect.
313      * @param formats are the formats you want to use for each limit.
314      * They can be either Format objects or Strings.
315      * When formatting with object Y,
316      * if the object is a NumberFormat, then ((NumberFormat) Y).format(X)
317      * is called. Otherwise Y.toString() is called.
318      */

319     public void setChoices(double[] limits, String JavaDoc formats[]) {
320         if (limits.length != formats.length) {
321             throw new IllegalArgumentException JavaDoc(
322                 "Array and limit arrays must be of the same length.");
323         }
324         choiceLimits = limits;
325         choiceFormats = formats;
326     }
327
328     /**
329      * Get the limits passed in the constructor.
330      * @return the limits.
331      */

332     public double[] getLimits() {
333         return choiceLimits;
334     }
335
336     /**
337      * Get the formats passed in the constructor.
338      * @return the formats.
339      */

340     public Object JavaDoc[] getFormats() {
341         return choiceFormats;
342     }
343
344     // Overrides
345

346     /**
347      * Specialization of format. This method really calls
348      * <code>format(double, StringBuffer, FieldPosition)</code>
349      * thus the range of longs that are supported is only equal to
350      * the range that can be stored by double. This will never be
351      * a practical limitation.
352      */

353     public StringBuffer JavaDoc format(long number, StringBuffer JavaDoc toAppendTo,
354                                FieldPosition JavaDoc status) {
355         return format((double)number, toAppendTo, status);
356     }
357
358     /**
359      * Returns pattern with formatted double.
360      * @param number number to be formatted & substituted.
361      * @param toAppendTo where text is appended.
362      * @param status ignore no useful status is returned.
363      */

364    public StringBuffer JavaDoc format(double number, StringBuffer JavaDoc toAppendTo,
365                                FieldPosition JavaDoc status) {
366         // find the number
367
int i;
368         for (i = 0; i < choiceLimits.length; ++i) {
369             if (!(number >= choiceLimits[i])) {
370                 // same as number < choiceLimits, except catchs NaN
371
break;
372             }
373         }
374         --i;
375         if (i < 0) i = 0;
376         // return either a formatted number, or a string
377
return toAppendTo.append(choiceFormats[i]);
378     }
379
380     /**
381      * Parses a Number from the input text.
382      * @param text the source text.
383      * @param status an input-output parameter. On input, the
384      * status.index field indicates the first character of the
385      * source text that should be parsed. On exit, if no error
386      * occured, status.index is set to the first unparsed character
387      * in the source text. On exit, if an error did occur,
388      * status.index is unchanged and status.errorIndex is set to the
389      * first index of the character that caused the parse to fail.
390      * @return A Number representing the value of the number parsed.
391      */

392     public Number JavaDoc parse(String JavaDoc text, ParsePosition JavaDoc status) {
393         // find the best number (defined as the one with the longest parse)
394
int start = status.index;
395         int furthest = start;
396         double bestNumber = Double.NaN;
397         double tempNumber = 0.0;
398         for (int i = 0; i < choiceFormats.length; ++i) {
399             String JavaDoc tempString = choiceFormats[i];
400             if (text.regionMatches(start, tempString, 0, tempString.length())) {
401                 status.index = start + tempString.length();
402                 tempNumber = choiceLimits[i];
403                 if (status.index > furthest) {
404                     furthest = status.index;
405                     bestNumber = tempNumber;
406                     if (furthest == text.length()) break;
407                 }
408             }
409         }
410         status.index = furthest;
411         if (status.index == start) {
412             status.errorIndex = furthest;
413         }
414         return new Double JavaDoc(bestNumber);
415     }
416
417     /**
418      * Finds the least double greater than d.
419      * If NaN, returns same value.
420      * <p>Used to make half-open intervals.
421      * @see #previousDouble
422      */

423     public static final double nextDouble (double d) {
424         return nextDouble(d,true);
425     }
426
427     /**
428      * Finds the greatest double less than d.
429      * If NaN, returns same value.
430      * @see #nextDouble
431      */

432     public static final double previousDouble (double d) {
433         return nextDouble(d,false);
434     }
435
436     /**
437      * Overrides Cloneable
438      */

439     public Object JavaDoc clone()
440     {
441         ChoiceFormat JavaDoc other = (ChoiceFormat JavaDoc) super.clone();
442         // for primitives or immutables, shallow clone is enough
443
other.choiceLimits = (double[]) choiceLimits.clone();
444         other.choiceFormats = (String JavaDoc[]) choiceFormats.clone();
445         return other;
446     }
447
448     /**
449      * Generates a hash code for the message format object.
450      */

451     public int hashCode() {
452         int result = choiceLimits.length;
453         if (choiceFormats.length > 0) {
454             // enough for reasonable distribution
455
result ^= choiceFormats[choiceFormats.length-1].hashCode();
456         }
457         return result;
458     }
459
460     /**
461      * Equality comparision between two
462      */

463     public boolean equals(Object JavaDoc obj) {
464         if (obj == null) return false;
465         if (this == obj) // quick check
466
return true;
467         if (getClass() != obj.getClass())
468             return false;
469         ChoiceFormat JavaDoc other = (ChoiceFormat JavaDoc) obj;
470         return (Utility.arrayEquals(choiceLimits,other.choiceLimits)
471             && Utility.arrayEquals(choiceFormats,other.choiceFormats));
472     }
473
474     /**
475      * After reading an object from the input stream, do a simple verification
476      * to maintain class invariants.
477      * @throws InvalidObjectException if the objects read from the stream is invalid.
478      */

479     private void readObject(ObjectInputStream JavaDoc in) throws IOException JavaDoc, ClassNotFoundException JavaDoc {
480         in.defaultReadObject();
481         if (choiceLimits.length != choiceFormats.length) {
482             throw new InvalidObjectException JavaDoc(
483                     "limits and format arrays of different length.");
484         }
485     }
486
487     // ===============privates===========================
488

489     /**
490      * A list of lower bounds for the choices. The formatter will return
491      * <code>choiceFormats[i]</code> if the number being formatted is greater than or equal to
492      * <code>choiceLimits[i]</code> and less than <code>choiceLimits[i+1]</code>.
493      * @serial
494      */

495     private double[] choiceLimits;
496
497     /**
498      * A list of choice strings. The formatter will return
499      * <code>choiceFormats[i]</code> if the number being formatted is greater than or equal to
500      * <code>choiceLimits[i]</code> and less than <code>choiceLimits[i+1]</code>.
501      * @serial
502      */

503     private String JavaDoc[] choiceFormats;
504
505     /*
506     static final long SIGN = 0x8000000000000000L;
507     static final long EXPONENT = 0x7FF0000000000000L;
508     static final long SIGNIFICAND = 0x000FFFFFFFFFFFFFL;
509
510     private static double nextDouble (double d, boolean positive) {
511         if (Double.isNaN(d) || Double.isInfinite(d)) {
512                 return d;
513             }
514         long bits = Double.doubleToLongBits(d);
515         long significand = bits & SIGNIFICAND;
516         if (bits < 0) {
517             significand |= (SIGN | EXPONENT);
518         }
519         long exponent = bits & EXPONENT;
520         if (positive) {
521             significand += 1;
522             // FIXME fix overflow & underflow
523         } else {
524             significand -= 1;
525             // FIXME fix overflow & underflow
526         }
527         bits = exponent | (significand & ~EXPONENT);
528         return Double.longBitsToDouble(bits);
529     }
530     */

531
532     static final long SIGN = 0x8000000000000000L;
533     static final long EXPONENT = 0x7FF0000000000000L;
534     static final long POSITIVEINFINITY = 0x7FF0000000000000L;
535
536     /**
537      * Finds the least double greater than d (if positive == true),
538      * or the greatest double less than d (if positive == false).
539      * If NaN, returns same value.
540      *
541      * Does not affect floating-point flags,
542      * provided these member functions do not:
543      * Double.longBitsToDouble(long)
544      * Double.doubleToLongBits(double)
545      * Double.isNaN(double)
546      */

547     public static double nextDouble (double d, boolean positive) {
548
549         /* filter out NaN's */
550         if (Double.isNaN(d)) {
551             return d;
552         }
553
554         /* zero's are also a special case */
555         if (d == 0.0) {
556             double smallestPositiveDouble = Double.longBitsToDouble(1L);
557             if (positive) {
558                 return smallestPositiveDouble;
559             } else {
560                 return -smallestPositiveDouble;
561             }
562         }
563
564         /* if entering here, d is a nonzero value */
565
566         /* hold all bits in a long for later use */
567         long bits = Double.doubleToLongBits(d);
568
569         /* strip off the sign bit */
570         long magnitude = bits & ~SIGN;
571
572         /* if next double away from zero, increase magnitude */
573         if ((bits > 0) == positive) {
574             if (magnitude != POSITIVEINFINITY) {
575                 magnitude += 1;
576             }
577         }
578         /* else decrease magnitude */
579         else {
580             magnitude -= 1;
581         }
582
583         /* restore sign bit and return */
584         long signbit = bits & SIGN;
585         return Double.longBitsToDouble (magnitude | signbit);
586     }
587
588     private static double[] doubleArraySize(double[] array) {
589         int oldSize = array.length;
590         double[] newArray = new double[oldSize * 2];
591         System.arraycopy(array, 0, newArray, 0, oldSize);
592         return newArray;
593     }
594     
595     private String JavaDoc[] doubleArraySize(String JavaDoc[] array) {
596         int oldSize = array.length;
597         String JavaDoc[] newArray = new String JavaDoc[oldSize * 2];
598         System.arraycopy(array, 0, newArray, 0, oldSize);
599         return newArray;
600     }
601
602 }
603
604
Popular Tags