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    
005    This file is part of GNU Classpath.
006    
007    GNU Classpath is free software; you can redistribute it and/or modify
008    it under the terms of the GNU General Public License as published by
009    the Free Software Foundation; either version 2, or (at your option)
010    any later version.
011    
012    GNU Classpath is distributed in the hope that it will be useful, but
013    WITHOUT ANY WARRANTY; without even the implied warranty of
014    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015    General Public License for more details.
016    
017    You should have received a copy of the GNU General Public License
018    along with GNU Classpath; see the file COPYING.  If not, write to the
019    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
020    02110-1301 USA.
021    
022    Linking this library statically or dynamically with other modules is
023    making a combined work based on this library.  Thus, the terms and
024    conditions of the GNU General Public License cover the whole
025    combination.
026    
027    As a special exception, the copyright holders of this library give you
028    permission to link this library with independent modules to produce an
029    executable, regardless of the license terms of these independent
030    modules, and to copy and distribute the resulting executable under
031    terms of your choice, provided that you also meet, for each linked
032    independent module, the terms and conditions of the license of that
033    module.  An independent module is a module which is not derived from
034    or based on this library.  If you modify this library, you may extend
035    this exception to your version of the library, but you are not
036    obligated to do so.  If you do not wish to do so, delete this
037    exception statement from your version. */
038    
039    
040    package java.text;
041    
042    import gnu.java.lang.CPStringBuilder;
043    
044    import 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) }
060    formats = { "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     */
084    public 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    }