Coverage Report - org.apache.commons.math.fraction.FractionFormat

Classes in this File Line Coverage Branch Coverage Complexity
FractionFormat
81% 
91% 
2

 1  
 /*
 2  
  * Copyright 2005 The Apache Software Foundation.
 3  
  *
 4  
  * Licensed under the Apache License, Version 2.0 (the "License");
 5  
  * you may not use this file except in compliance with the License.
 6  
  * You may obtain a copy of the License at
 7  
  *
 8  
  *      http://www.apache.org/licenses/LICENSE-2.0
 9  
  *
 10  
  * Unless required by applicable law or agreed to in writing, software
 11  
  * distributed under the License is distributed on an "AS IS" BASIS,
 12  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  
  * See the License for the specific language governing permissions and
 14  
  * limitations under the License.
 15  
  */
 16  
 
 17  
 package org.apache.commons.math.fraction;
 18  
 
 19  
 import java.io.Serializable;
 20  
 import java.text.FieldPosition;
 21  
 import java.text.Format;
 22  
 import java.text.NumberFormat;
 23  
 import java.text.ParseException;
 24  
 import java.text.ParsePosition;
 25  
 import java.util.Locale;
 26  
 
 27  
 import org.apache.commons.math.ConvergenceException;
 28  
 
 29  
 /**
 30  
  * Formats a Fraction number in proper format or improper format.  The number
 31  
  * format for each of the whole number, numerator and, denominator can be
 32  
  * configured.
 33  
  *
 34  
  * @since 1.1
 35  
  * @version $Revision$ $Date: 2005-06-26 20:46:57 -0700 (Sun, 26 Jun 2005) $
 36  
  */
 37  
 public class FractionFormat extends Format implements Serializable {
 38  
     
 39  
     /** Serializable version identifier */
 40  
     static final long serialVersionUID = -6337346779577272306L;
 41  
 
 42  
     /** The format used for the denominator. */
 43  
     private NumberFormat denominatorFormat;
 44  
 
 45  
     /** The format used for the numerator. */
 46  
     private NumberFormat numeratorFormat;
 47  
     
 48  
     /**
 49  
      * Create an improper formatting instance with the default number format
 50  
      * for the numerator and denominator.  
 51  
      */
 52  
     public FractionFormat() {
 53  0
         this(getDefaultNumberFormat());
 54  0
     }
 55  
 
 56  
     /**
 57  
      * Create an improper formatting instance with a custom number format for
 58  
      * both the numerator and denominator.
 59  
      * @param format the custom format for both the numerator and denominator.
 60  
      */
 61  
     public FractionFormat(NumberFormat format) {
 62  30
         this(format, (NumberFormat)format.clone());
 63  30
     }
 64  
 
 65  
     /**
 66  
      * Create an improper formatting instance with a custom number format for
 67  
      * the numerator and a custom number format for the denominator.
 68  
      * @param numeratorFormat the custom format for the numerator.
 69  
      * @param denominatorFormat the custom format for the denominator.
 70  
      */
 71  
     public FractionFormat(NumberFormat numeratorFormat,
 72  
             NumberFormat denominatorFormat)
 73  
     {
 74  60
         super();
 75  60
         this.numeratorFormat = numeratorFormat;
 76  60
         this.denominatorFormat = denominatorFormat;
 77  60
     }
 78  
 
 79  
     /**
 80  
      * This static method calls formatFraction() on a default instance of
 81  
      * FractionFormat.
 82  
      *
 83  
      * @param f Fraction object to format
 84  
      * @return A formatted fraction in proper form.
 85  
      */
 86  
     public static String formatFraction(Fraction f) {
 87  0
         return getImproperInstance().format(f);
 88  
     }
 89  
     
 90  
     /**
 91  
      * Get the set of locales for which complex formats are available.  This
 92  
      * is the same set as the {@link NumberFormat} set. 
 93  
      * @return available complex format locales.
 94  
      */
 95  
     public static Locale[] getAvailableLocales() {
 96  0
         return NumberFormat.getAvailableLocales();
 97  
     }
 98  
     
 99  
     /**
 100  
      * Returns the default complex format for the current locale.
 101  
      * @return the default complex format.
 102  
      */
 103  
     public static FractionFormat getImproperInstance() {
 104  0
         return getImproperInstance(Locale.getDefault());
 105  
     }
 106  
     
 107  
     /**
 108  
      * Returns the default complex format for the given locale.
 109  
      * @param locale the specific locale used by the format.
 110  
      * @return the complex format specific to the given locale.
 111  
      */
 112  
     public static FractionFormat getImproperInstance(Locale locale) {
 113  30
         NumberFormat f = getDefaultNumberFormat(locale);
 114  30
         return new FractionFormat(f);
 115  
     }
 116  
     
 117  
     /**
 118  
      * Returns the default complex format for the current locale.
 119  
      * @return the default complex format.
 120  
      */
 121  
     public static FractionFormat getProperInstance() {
 122  0
         return getProperInstance(Locale.getDefault());
 123  
     }
 124  
     
 125  
     /**
 126  
      * Returns the default complex format for the given locale.
 127  
      * @param locale the specific locale used by the format.
 128  
      * @return the complex format specific to the given locale.
 129  
      */
 130  
     public static FractionFormat getProperInstance(Locale locale) {
 131  30
         NumberFormat f = getDefaultNumberFormat(locale);
 132  30
         return new ProperFractionFormat(f);
 133  
     }
 134  
     
 135  
     /**
 136  
      * Create a default number format.  The default number format is based on
 137  
      * {@link NumberFormat#getNumberInstance(java.util.Locale)} with the only
 138  
      * customizing is the maximum number of fraction digits, which is set to 0.  
 139  
      * @return the default number format.
 140  
      */
 141  
     protected static NumberFormat getDefaultNumberFormat() {
 142  0
         return getDefaultNumberFormat(Locale.getDefault());
 143  
     }
 144  
     
 145  
     /**
 146  
      * Create a default number format.  The default number format is based on
 147  
      * {@link NumberFormat#getNumberInstance(java.util.Locale)} with the only
 148  
      * customizing is the maximum number of fraction digits, which is set to 0.  
 149  
      * @param locale the specific locale used by the format.
 150  
      * @return the default number format specific to the given locale.
 151  
      */
 152  
     private static NumberFormat getDefaultNumberFormat(Locale locale) {
 153  60
         NumberFormat nf = NumberFormat.getNumberInstance(locale);
 154  60
         nf.setMaximumFractionDigits(0);
 155  60
         nf.setParseIntegerOnly(true);
 156  60
         return nf;
 157  
     }
 158  
     
 159  
     /**
 160  
      * Formats a {@link Fraction} object to produce a string.  The fraction is
 161  
      * output in improper format.
 162  
      *
 163  
      * @param fraction the object to format.
 164  
      * @param toAppendTo where the text is to be appended
 165  
      * @param pos On input: an alignment field, if desired. On output: the
 166  
      *            offsets of the alignment field
 167  
      * @return the value passed in as toAppendTo.
 168  
      */
 169  
     public StringBuffer format(Fraction fraction, StringBuffer toAppendTo,
 170  
             FieldPosition pos) {
 171  
         
 172  10
         pos.setBeginIndex(0);
 173  10
         pos.setEndIndex(0);
 174  
 
 175  10
         getNumeratorFormat().format(fraction.getNumerator(), toAppendTo, pos);
 176  10
         toAppendTo.append(" / ");
 177  10
         getDenominatorFormat().format(fraction.getDenominator(), toAppendTo,
 178  
             pos);
 179  
         
 180  10
         return toAppendTo;
 181  
     }
 182  
     
 183  
     /**
 184  
      * Formats a object to produce a string.  <code>obj</code> must be either a 
 185  
      * {@link Fraction} object or a {@link Number} object.  Any other type of
 186  
      * object will result in an {@link IllegalArgumentException} being thrown.
 187  
      *
 188  
      * @param obj the object to format.
 189  
      * @param toAppendTo where the text is to be appended
 190  
      * @param pos On input: an alignment field, if desired. On output: the
 191  
      *            offsets of the alignment field
 192  
      * @return the value passed in as toAppendTo.
 193  
      * @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition)
 194  
      * @throws IllegalArgumentException is <code>obj</code> is not a valid type.
 195  
      */
 196  
     public StringBuffer format(Object obj, StringBuffer toAppendTo,
 197  
             FieldPosition pos)
 198  
     {
 199  20
         StringBuffer ret = null;
 200  
         
 201  20
         if (obj instanceof Fraction) {
 202  20
             ret = format( (Fraction)obj, toAppendTo, pos);
 203  0
         } else if (obj instanceof Number) {
 204  
             try {
 205  0
                 ret = format( new Fraction(((Number)obj).doubleValue()),
 206  
                     toAppendTo, pos);
 207  0
             } catch (ConvergenceException ex) {
 208  0
                 throw new IllegalArgumentException(
 209  
                     "Cannot convert given object to a fraction.");
 210  0
             }
 211  
         } else { 
 212  0
             throw new IllegalArgumentException(
 213  
                 "Cannot format given object as a fraction");
 214  
         }
 215  
         
 216  20
         return ret;
 217  
     }
 218  
 
 219  
     /**
 220  
      * Access the denominator format.
 221  
      * @return the denominator format.
 222  
      */
 223  
     public NumberFormat getDenominatorFormat() {
 224  48
         return denominatorFormat;
 225  
     }
 226  
     
 227  
     /**
 228  
      * Access the numerator format.
 229  
      * @return the numerator format.
 230  
      */
 231  
     public NumberFormat getNumeratorFormat() {
 232  66
         return numeratorFormat;
 233  
     }
 234  
 
 235  
     /**
 236  
      * Parses a string to produce a {@link Fraction} object.
 237  
      * @param source the string to parse
 238  
      * @return the parsed {@link Fraction} object.
 239  
      * @exception ParseException if the beginning of the specified string
 240  
      *            cannot be parsed.
 241  
      */
 242  
     public Fraction parse(String source) throws ParseException {
 243  32
         ParsePosition parsePosition = new ParsePosition(0);
 244  32
         Fraction result = parse(source, parsePosition);
 245  32
         if (parsePosition.getIndex() == 0) {
 246  12
             throw new ParseException("Unparseable fraction number: \"" +
 247  
                 source + "\"", parsePosition.getErrorIndex());
 248  
         }
 249  20
         return result;
 250  
     }
 251  
     
 252  
     /**
 253  
      * Parses a string to produce a {@link Fraction} object.  This method
 254  
      * expects the string to be formatted as an improper fraction.  
 255  
      * @param source the string to parse
 256  
      * @param pos input/ouput parsing parameter.
 257  
      * @return the parsed {@link Fraction} object.
 258  
      */
 259  
     public Fraction parse(String source, ParsePosition pos) {
 260  32
         int initialIndex = pos.getIndex();
 261  
 
 262  
         // parse whitespace
 263  32
         parseAndIgnoreWhitespace(source, pos);
 264  
 
 265  
         // parse numerator
 266  32
         Number num = getNumeratorFormat().parse(source, pos);
 267  32
         if (num == null) {
 268  
             // invalid integer number
 269  
             // set index back to initial, error index should already be set
 270  
             // character examined.
 271  4
             pos.setIndex(initialIndex);
 272  4
             return null;
 273  
         }
 274  
 
 275  
         // parse '/'
 276  28
         int startIndex = pos.getIndex();
 277  28
         char c = parseNextCharacter(source, pos);
 278  28
         switch (c) {
 279  
         case 0 :
 280  
             // no '/'
 281  
             // return num as a fraction
 282  4
             return new Fraction(num.intValue(), 1);
 283  
         case '/' :
 284  
             // found '/', continue parsing denominator
 285  16
             break;
 286  
         default :
 287  
             // invalid '/'
 288  
             // set index back to initial, error index should be the last
 289  
             // character examined.
 290  8
             pos.setIndex(initialIndex);
 291  8
             pos.setErrorIndex(startIndex);
 292  8
             return null;
 293  
         }
 294  
 
 295  
         // parse whitespace
 296  16
         parseAndIgnoreWhitespace(source, pos);
 297  
 
 298  
         // parse denominator
 299  16
         Number den = getDenominatorFormat().parse(source, pos);
 300  16
         if (den == null) {
 301  
             // invalid integer number
 302  
             // set index back to initial, error index should already be set
 303  
             // character examined.
 304  4
             pos.setIndex(initialIndex);
 305  4
             return null;
 306  
         }
 307  
 
 308  12
         return new Fraction(num.intValue(), den.intValue());
 309  
     }
 310  
 
 311  
     /**
 312  
      * Parses a string to produce a object.
 313  
      * @param source the string to parse
 314  
      * @param pos input/ouput parsing parameter.
 315  
      * @return the parsed object.
 316  
      * @see java.text.Format#parseObject(java.lang.String, java.text.ParsePosition)
 317  
      */
 318  
     public Object parseObject(String source, ParsePosition pos) {
 319  0
         return parse(source, pos);
 320  
     }
 321  
     
 322  
     /**
 323  
      * Modify the denominator format.
 324  
      * @param format the new denominator format value.
 325  
      * @throws IllegalArgumentException if <code>format</code> is
 326  
      *         <code>null</code>.
 327  
      */
 328  
     public void setDenominatorFormat(NumberFormat format) {
 329  8
         if (format == null) {
 330  0
             throw new IllegalArgumentException(
 331  
                 "denominator format can not be null.");
 332  
         }
 333  8
         this.denominatorFormat = format;
 334  8
     }
 335  
     
 336  
     /**
 337  
      * Modify the numerator format.
 338  
      * @param format the new numerator format value.
 339  
      * @throws IllegalArgumentException if <code>format</code> is
 340  
      *         <code>null</code>.
 341  
      */
 342  
     public void setNumeratorFormat(NumberFormat format) {
 343  8
         if (format == null) {
 344  0
             throw new IllegalArgumentException(
 345  
                 "numerator format can not be null.");
 346  
         }
 347  8
         this.numeratorFormat = format;
 348  8
     }
 349  
      
 350  
     /**
 351  
      * Parses <code>source</code> until a non-whitespace character is found.
 352  
      * @param source the string to parse
 353  
      * @param pos input/ouput parsing parameter.  On output, <code>pos</code>
 354  
      *        holds the index of the next non-whitespace character.
 355  
      */
 356  
     protected static void parseAndIgnoreWhitespace(
 357  
         String source, ParsePosition pos)
 358  
     {
 359  66
         parseNextCharacter(source, pos);
 360  66
         pos.setIndex(pos.getIndex() - 1);
 361  66
     }
 362  
 
 363  
     /**
 364  
      * Parses <code>source</code> until a non-whitespace character is found.
 365  
      * @param source the string to parse
 366  
      * @param pos input/ouput parsing parameter.
 367  
      * @return the first non-whitespace character.
 368  
      */
 369  
     protected static char parseNextCharacter(String source, ParsePosition pos) {
 370  98
          int index = pos.getIndex();
 371  98
          int n = source.length();
 372  98
          char ret = 0;
 373  
 
 374  98
          if (index < n) {
 375  
              char c;
 376  
              do {
 377  148
                  c = source.charAt(index++);
 378  148
              } while (Character.isWhitespace(c) && index < n);
 379  94
              pos.setIndex(index);
 380  
          
 381  94
              if (index < n) {
 382  72
                  ret = c;
 383  
              }
 384  
          }
 385  
          
 386  98
          return ret;
 387  
     }
 388  
 }