1 /* ChoiceFormat.java -- Format over a range of numbers
2    Copyright (C) 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc.
3 
4 This file is part of GNU Classpath.
5 
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10 
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
19 02111-1307 USA.
20 
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25 
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37 
38 
39 package java.text;
40 
41 import java.util.Vector;
42 
43 /**
44  * This class allows a format to be specified based on a range of numbers.
45  * To use this class, first specify two lists of formats and range terminators.
46  * These lists must be arrays of equal length.  The format of index
47  * <code>i</code> will be selected for value <code>X</code> if
48  * <code>terminator[i] <= X < limit[i + 1]</code>.  If the value X is not
49  * included in any range, then either the first or last format will be
50  * used depending on whether the value X falls outside the range.
51  * <p>
52  * This sounds complicated, but that is because I did a poor job of
53  * explaining it.  Consider the following example:
54  * <p>
55  *
56 <pre>terminators = { 1, ChoiceFormat.nextDouble(1) }
57 formats = { "file", "files" }</pre>
58  *
59  * <p>
60  * In this case if the actual number tested is one or less, then the word
61  * "file" is used as the format value.  If the number tested is greater than
62  * one, then "files" is used.  This allows plurals to be handled
63  * gracefully.  Note the use of the method <code>nextDouble</code>.  This
64  * method selects the next highest double number than its argument.  This
65  * effectively makes any double greater than 1.0 cause the "files" string
66  * to be selected.  (Note that all terminator values are specified as
67  * doubles.
68  * <p>
69  * Note that in order for this class to work properly, the range terminator
70  * array must be sorted in ascending order and the format string array
71  * must be the same length as the terminator array.
72  *
73  * @author Tom Tromey <tromey@cygnus.com>
74  * @author Aaron M. Renn (arenn@urbanophile.com)
75  * @date March 9, 1999
76  */
77 /* Written using "Java Class Libraries", 2nd edition, plus online
78  * API docs for JDK 1.2 from http://www.javasoft.com.
79  * Status:  Believed complete and correct to 1.1.
80  */
81 public class ChoiceFormat extends NumberFormat
82 {
83   /**
84    * This method sets new range terminators and format strings for this
85    * object based on the specified pattern. This pattern is of the form
86    * "term#string|term#string...".  For example "1#Sunday|2#Monday|#Tuesday".
87    *
88    * @param pattern The pattern of terminators and format strings.
89    *
90    * @exception IllegalArgumentException If the pattern is not valid
91    */
applyPattern(String newPattern)92   public void applyPattern (String newPattern)
93   {
94     // Note: we assume the same kind of quoting rules apply here.
95     // This isn't explicitly documented.  But for instance we accept
96     // '#' as a literal hash in a format string.
97     int index = 0, max = newPattern.length();
98     Vector stringVec = new Vector ();
99     Vector limitVec = new Vector ();
100     StringBuffer buf = new StringBuffer ();
101 
102     while (true)
103       {
104 	// Find end of double.
105 	int dstart = index;
106 	while (index < max)
107 	  {
108 	    char c = newPattern.charAt(index);
109 	    if (c == '#' || c == '\u2064' || c == '<')
110 	      break;
111 	    ++index;
112 	  }
113 
114 	if (index == max)
115 	  throw new IllegalArgumentException ("unexpected end of text");
116 	Double d = new Double (newPattern.substring(dstart, index));
117 
118 	if (newPattern.charAt(index) == '<')
119 	  d = new Double (nextDouble (d.doubleValue()));
120 
121 	limitVec.addElement(d);
122 
123 	// Scan text.
124 	++index;
125 	buf.setLength(0);
126 	while (index < max)
127 	  {
128 	    char c = newPattern.charAt(index);
129 	    if (c == '\'' && index < max + 1
130 		&& newPattern.charAt(index + 1) == '\'')
131 	      {
132 		buf.append(c);
133 		++index;
134 	      }
135 	    else if (c == '\'' && index < max + 2)
136 	      {
137 		buf.append(newPattern.charAt(index + 1));
138 		index += 2;
139 	      }
140 	    else if (c == '|')
141 	      break;
142 	    else
143 	      buf.append(c);
144 	    ++index;
145 	  }
146 
147 	stringVec.addElement(buf.toString());
148 	if (index == max)
149 	  break;
150 	++index;
151       }
152 
153     choiceFormats = new String[stringVec.size()];
154     stringVec.copyInto(choiceFormats);
155 
156     choiceLimits = new double[limitVec.size()];
157     for (int i = 0; i < choiceLimits.length; ++i)
158       {
159 	Double d = (Double) limitVec.elementAt(i);
160 	choiceLimits[i] = d.doubleValue();
161       }
162   }
163 
164   /**
165    * This method initializes a new instance of <code>ChoiceFormat</code> that
166    * generates its range terminator and format string arrays from the
167    * specified pattern.  This pattern is of the form
168    * "term#string|term#string...".  For example "1#Sunday|2#Monday|#Tuesday".
169    * This is the same pattern type used by the <code>applyPattern</code>
170    * method.
171    *
172    * @param pattern The pattern of terminators and format strings.
173    *
174    * @exception IllegalArgumentException If the pattern is not valid
175    */
ChoiceFormat(String newPattern)176   public ChoiceFormat (String newPattern)
177   {
178     super ();
179     applyPattern (newPattern);
180   }
181 
182   /**
183    * This method initializes a new instance of <code>ChoiceFormat</code> that
184    * will use the specified range terminators and format strings.
185    *
186    * @param choiceLimits The array of range terminators
187    * @param choiceFormats The array of format strings
188    */
ChoiceFormat(double[] choiceLimits, String[] choiceFormats)189   public ChoiceFormat (double[] choiceLimits, String[] choiceFormats)
190   {
191     super ();
192     setChoices (choiceLimits, choiceFormats);
193   }
194 
195   /**
196    * This method tests this object for equality with the specified
197    * object.  This will be true if and only if:
198    * <ul>
199    * <li>The specified object is not <code>null</code>.
200    * <li>The specified object is an instance of <code>ChoiceFormat</code>.
201    * <li>The termination ranges and format strings are identical to
202    *     this object's.
203    * </ul>
204    *
205    * @param obj The object to test for equality against.
206    *
207    * @return <code>true</code> if the specified object is equal to
208    * this one, <code>false</code> otherwise.
209    */
equals(Object obj)210   public boolean equals (Object obj)
211   {
212     if (! (obj instanceof ChoiceFormat))
213       return false;
214     ChoiceFormat cf = (ChoiceFormat) obj;
215     if (choiceLimits.length != cf.choiceLimits.length)
216       return false;
217     for (int i = choiceLimits.length - 1; i >= 0; --i)
218       {
219 	if (choiceLimits[i] != cf.choiceLimits[i]
220 	    || !choiceFormats[i].equals(cf.choiceFormats[i]))
221 	  return false;
222       }
223     return true;
224   }
225 
226   /**
227    * This method appends the appropriate format string to the specified
228    * <code>StringBuffer</code> based on the supplied <code>long</code>
229    * argument.
230    *
231    * @param number The number used for determine (based on the range
232    *               terminators) which format string to append.
233    * @param sb The <code>StringBuffer</code> to append the format string to.
234    * @param status Unused.
235    *
236    * @return The <code>StringBuffer</code> with the format string appended.
237    */
format(long num, StringBuffer appendBuf, FieldPosition pos)238   public StringBuffer format (long num, StringBuffer appendBuf,
239 			      FieldPosition pos)
240   {
241     return format ((double) num, appendBuf, pos);
242   }
243 
244   /**
245    * This method appends the appropriate format string to the specified
246    * <code>StringBuffer</code> based on the supplied <code>double</code>
247    * argument.
248    *
249    * @param number The number used for determine (based on the range
250    *               terminators) which format string to append.
251    * @param sb The <code>StringBuffer</code> to append the format string to.
252    * @param status Unused.
253    *
254    * @return The <code>StringBuffer</code> with the format string appended.
255    */
format(double num, StringBuffer appendBuf, FieldPosition pos)256   public StringBuffer format (double num, StringBuffer appendBuf,
257 			      FieldPosition pos)
258   {
259     if (choiceLimits.length == 0)
260       return appendBuf;
261 
262     int index = 0;
263     if (! Double.isNaN(num) && num >= choiceLimits[0])
264       {
265 	for (; index < choiceLimits.length - 1; ++index)
266 	  {
267 	    if (choiceLimits[index] <= num && num < choiceLimits[index + 1])
268 	      break;
269 	  }
270       }
271 
272     return appendBuf.append(choiceFormats[index]);
273   }
274 
275   /**
276    * This method returns the list of format strings in use.
277    *
278    * @return The list of format objects.
279    */
getFormats()280   public Object[] getFormats ()
281   {
282     return (Object[]) choiceFormats.clone();
283   }
284 
285   /**
286    * This method returns the list of range terminators in use.
287    *
288    * @return The list of range terminators.
289    */
getLimits()290   public double[] getLimits ()
291   {
292     return (double[]) choiceLimits.clone();
293   }
294 
295   /**
296    * This method returns a hash value for this object
297    *
298    * @return A hash value for this object.
299    */
hashCode()300   public int hashCode ()
301   {
302     int hash = 0;
303     for (int i = 0; i < choiceLimits.length; ++i)
304       {
305 	long v = Double.doubleToLongBits(choiceLimits[i]);
306 	hash ^= (v ^ (v >>> 32));
307 	hash ^= choiceFormats[i].hashCode();
308       }
309     return hash;
310   }
311 
312   /**
313    * This method returns the lowest possible double greater than the
314    * specified double.  If the specified double value is equal to
315    * <code>Double.NaN</code> then that is the value returned.
316    *
317    * @param d The specified double
318    *
319    * @return The lowest double value greater than the specified double.
320    */
nextDouble(double d)321   public static final double nextDouble (double d)
322   {
323     return nextDouble (d, true);
324   }
325 
326   /**
327    * This method returns a double that is either the next highest double
328    * or next lowest double compared to the specified double depending on the
329    * value of the passed boolean parameter.  If the boolean parameter is
330    * <code>true</code>, then the lowest possible double greater than the
331    * specified double will be returned.  Otherwise the highest possible
332    * double less than the specified double will be returned.
333    *
334    * @param d The specified double
335    * @param positive <code>true</code> to return the next highest
336    *                 double, <code>false</code> otherwise.
337    *
338    * @return The next highest or lowest double value.
339    */
nextDouble(double d, boolean next)340   public static double nextDouble (double d, boolean next)
341   {
342     if (Double.isInfinite(d) || Double.isNaN(d))
343       return d;
344 
345     long bits = Double.doubleToLongBits(d);
346 
347     long mantMask = (1L << mantissaBits) - 1;
348     long mantissa = bits & mantMask;
349 
350     long expMask = (1L << exponentBits) - 1;
351     long exponent = (bits >>> mantissaBits) & expMask;
352 
353     if (next ^ (bits < 0)) // Increment magnitude
354       {
355 	if (mantissa == (1L << mantissaBits) - 1)
356 	  {
357 	    mantissa = 0L;
358 	    exponent++;
359 
360 	    // Check for absolute overflow.
361 	    if (exponent >= (1L << mantissaBits))
362 	      return (bits > 0) ? Double.POSITIVE_INFINITY
363 		: Double.NEGATIVE_INFINITY;
364 	  }
365 	else
366 	  mantissa++;
367       }
368     else // Decrement magnitude
369       {
370 	if (exponent == 0L && mantissa == 0L)
371 	  {
372 	    // The only case where there is a change of sign
373 	    return next ? Double.MIN_VALUE : -Double.MIN_VALUE;
374 	  }
375 	else
376 	  {
377 	    if (mantissa == 0L)
378 	      {
379 		mantissa = (1L << mantissaBits) - 1;
380 		exponent--;
381 	      }
382 	    else
383 	      mantissa--;
384 	  }
385       }
386 
387     long result = bits < 0 ? 1 : 0;
388     result = (result << exponentBits) | exponent;
389     result = (result << mantissaBits) | mantissa;
390     return Double.longBitsToDouble(result);
391   }
392 
393   /**
394    * I'm not sure what this method is really supposed to do, as it is
395    * not documented.
396    */
397   public Number parse (String sourceStr, ParsePosition pos)
398   {
399     int index = pos.getIndex();
400     for (int i = 0; i < choiceLimits.length; ++i)
401       {
402 	if (sourceStr.startsWith(choiceFormats[i], index))
403 	  {
404 	    pos.setIndex(index + choiceFormats[i].length());
405 	    return new Double (choiceLimits[i]);
406 	  }
407       }
408     pos.setErrorIndex(index);
409     return new Double (Double.NaN);
410   }
411 
412   /**
413    * This method returns the highest possible double less than the
414    * specified double.  If the specified double value is equal to
415    * <code>Double.NaN</code> then that is the value returned.
416    *
417    * @param d The specified double
418    *
419    * @return The highest double value less than the specified double.
420    */
421   public static final double previousDouble (double d)
422   {
423     return nextDouble (d, false);
424   }
425 
426   /**
427    * This method sets new range terminators and format strings for this
428    * object.
429    *
430    * @param choiceLimits The new range terminators
431    * @param choiceFormats The new choice formats
432    */
433   public void setChoices (double[] choiceLimits, String[] choiceFormats)
434   {
435     if (choiceLimits == null || choiceFormats == null)
436       throw new NullPointerException ();
437     if (choiceLimits.length != choiceFormats.length)
438       throw new IllegalArgumentException ();
439     this.choiceFormats = (String[]) choiceFormats.clone();
440     this.choiceLimits = (double[]) choiceLimits.clone();
441   }
442 
443   private final void quoteString (StringBuffer dest, String text)
444   {
445     int max = text.length();
446     for (int i = 0; i < max; ++i)
447       {
448 	char c = text.charAt(i);
449 	if (c == '\'')
450 	  {
451 	    dest.append(c);
452 	    dest.append(c);
453 	  }
454 	else if (c == '#' || c == '|' || c == '\u2064' || c == '<')
455 	  {
456 	    dest.append('\'');
457 	    dest.append(c);
458 	    dest.append('\'');
459 	  }
460 	else
461 	  dest.append(c);
462       }
463   }
464 
465   /**
466    * This method returns the range terminator list and format string list
467    * as a <code>String</code> suitable for using with the
468    * <code>applyPattern</code> method.
469    *
470    * @return A pattern string for this object
471    */
472   public String toPattern ()
473   {
474     StringBuffer result = new StringBuffer ();
475     for (int i = 0; i < choiceLimits.length; ++i)
476       {
477 	result.append(choiceLimits[i]);
478 	result.append('#');
479 	quoteString (result, choiceFormats[i]);
480       }
481     return result.toString();
482   }
483 
484   /**
485    * This is the list of format strings.  Note that this variable is
486    * specified by the serialization spec of this class.
487    */
488   private String[] choiceFormats;
489 
490   /**
491    * This is the list of range terminator values.  Note that this variable is
492    * specified by the serialization spec of this class.
493    */
494   private double[] choiceLimits;
495 
496   // Number of mantissa bits in double.
497   private static final int mantissaBits = 52;
498   // Number of exponent bits in a double.
499   private static final int exponentBits = 11;
500 
501   private static final long serialVersionUID = 1795184449645032964L;
502 }
503