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