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