Home | History | Annotate | Download | only in fraction
      1 /*
      2  * Licensed to the Apache Software Foundation (ASF) under one or more
      3  * contributor license agreements.  See the NOTICE file distributed with
      4  * this work for additional information regarding copyright ownership.
      5  * The ASF licenses this file to You under the Apache License, Version 2.0
      6  * (the "License"); you may not use this file except in compliance with
      7  * the License.  You may obtain a copy of the License at
      8  *
      9  *      http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 package org.apache.commons.math.fraction;
     18 
     19 import java.text.FieldPosition;
     20 import java.text.NumberFormat;
     21 import java.text.ParsePosition;
     22 
     23 import org.apache.commons.math.exception.util.LocalizedFormats;
     24 import org.apache.commons.math.exception.NullArgumentException;
     25 import org.apache.commons.math.util.MathUtils;
     26 
     27 /**
     28  * Formats a Fraction number in proper format.  The number format for each of
     29  * the whole number, numerator and, denominator can be configured.
     30  * <p>
     31  * Minus signs are only allowed in the whole number part - i.e.,
     32  * "-3 1/2" is legitimate and denotes -7/2, but "-3 -1/2" is invalid and
     33  * will result in a <code>ParseException</code>.</p>
     34  *
     35  * @since 1.1
     36  * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 aot 2010) $
     37  */
     38 public class ProperFractionFormat extends FractionFormat {
     39 
     40     /** Serializable version identifier */
     41     private static final long serialVersionUID = 760934726031766749L;
     42 
     43     /** The format used for the whole number. */
     44     private NumberFormat wholeFormat;
     45 
     46     /**
     47      * Create a proper formatting instance with the default number format for
     48      * the whole, numerator, and denominator.
     49      */
     50     public ProperFractionFormat() {
     51         this(getDefaultNumberFormat());
     52     }
     53 
     54     /**
     55      * Create a proper formatting instance with a custom number format for the
     56      * whole, numerator, and denominator.
     57      * @param format the custom format for the whole, numerator, and
     58      *        denominator.
     59      */
     60     public ProperFractionFormat(NumberFormat format) {
     61         this(format, (NumberFormat)format.clone(), (NumberFormat)format.clone());
     62     }
     63 
     64     /**
     65      * Create a proper formatting instance with a custom number format for each
     66      * of the whole, numerator, and denominator.
     67      * @param wholeFormat the custom format for the whole.
     68      * @param numeratorFormat the custom format for the numerator.
     69      * @param denominatorFormat the custom format for the denominator.
     70      */
     71     public ProperFractionFormat(NumberFormat wholeFormat,
     72             NumberFormat numeratorFormat,
     73             NumberFormat denominatorFormat)
     74     {
     75         super(numeratorFormat, denominatorFormat);
     76         setWholeFormat(wholeFormat);
     77     }
     78 
     79     /**
     80      * Formats a {@link Fraction} object to produce a string.  The fraction
     81      * is output in proper format.
     82      *
     83      * @param fraction the object to format.
     84      * @param toAppendTo where the text is to be appended
     85      * @param pos On input: an alignment field, if desired. On output: the
     86      *            offsets of the alignment field
     87      * @return the value passed in as toAppendTo.
     88      */
     89     @Override
     90     public StringBuffer format(Fraction fraction, StringBuffer toAppendTo,
     91             FieldPosition pos) {
     92 
     93         pos.setBeginIndex(0);
     94         pos.setEndIndex(0);
     95 
     96         int num = fraction.getNumerator();
     97         int den = fraction.getDenominator();
     98         int whole = num / den;
     99         num = num % den;
    100 
    101         if (whole != 0) {
    102             getWholeFormat().format(whole, toAppendTo, pos);
    103             toAppendTo.append(' ');
    104             num = Math.abs(num);
    105         }
    106         getNumeratorFormat().format(num, toAppendTo, pos);
    107         toAppendTo.append(" / ");
    108         getDenominatorFormat().format(den, toAppendTo,
    109             pos);
    110 
    111         return toAppendTo;
    112     }
    113 
    114     /**
    115      * Access the whole format.
    116      * @return the whole format.
    117      */
    118     public NumberFormat getWholeFormat() {
    119         return wholeFormat;
    120     }
    121 
    122     /**
    123      * Parses a string to produce a {@link Fraction} object.  This method
    124      * expects the string to be formatted as a proper fraction.
    125      * <p>
    126      * Minus signs are only allowed in the whole number part - i.e.,
    127      * "-3 1/2" is legitimate and denotes -7/2, but "-3 -1/2" is invalid and
    128      * will result in a <code>ParseException</code>.</p>
    129      *
    130      * @param source the string to parse
    131      * @param pos input/ouput parsing parameter.
    132      * @return the parsed {@link Fraction} object.
    133      */
    134     @Override
    135     public Fraction parse(String source, ParsePosition pos) {
    136         // try to parse improper fraction
    137         Fraction ret = super.parse(source, pos);
    138         if (ret != null) {
    139             return ret;
    140         }
    141 
    142         int initialIndex = pos.getIndex();
    143 
    144         // parse whitespace
    145         parseAndIgnoreWhitespace(source, pos);
    146 
    147         // parse whole
    148         Number whole = getWholeFormat().parse(source, pos);
    149         if (whole == null) {
    150             // invalid integer number
    151             // set index back to initial, error index should already be set
    152             // character examined.
    153             pos.setIndex(initialIndex);
    154             return null;
    155         }
    156 
    157         // parse whitespace
    158         parseAndIgnoreWhitespace(source, pos);
    159 
    160         // parse numerator
    161         Number num = getNumeratorFormat().parse(source, pos);
    162         if (num == null) {
    163             // invalid integer number
    164             // set index back to initial, error index should already be set
    165             // character examined.
    166             pos.setIndex(initialIndex);
    167             return null;
    168         }
    169 
    170         if (num.intValue() < 0) {
    171             // minus signs should be leading, invalid expression
    172             pos.setIndex(initialIndex);
    173             return null;
    174         }
    175 
    176         // parse '/'
    177         int startIndex = pos.getIndex();
    178         char c = parseNextCharacter(source, pos);
    179         switch (c) {
    180         case 0 :
    181             // no '/'
    182             // return num as a fraction
    183             return new Fraction(num.intValue(), 1);
    184         case '/' :
    185             // found '/', continue parsing denominator
    186             break;
    187         default :
    188             // invalid '/'
    189             // set index back to initial, error index should be the last
    190             // character examined.
    191             pos.setIndex(initialIndex);
    192             pos.setErrorIndex(startIndex);
    193             return null;
    194         }
    195 
    196         // parse whitespace
    197         parseAndIgnoreWhitespace(source, pos);
    198 
    199         // parse denominator
    200         Number den = getDenominatorFormat().parse(source, pos);
    201         if (den == null) {
    202             // invalid integer number
    203             // set index back to initial, error index should already be set
    204             // character examined.
    205             pos.setIndex(initialIndex);
    206             return null;
    207         }
    208 
    209         if (den.intValue() < 0) {
    210             // minus signs must be leading, invalid
    211             pos.setIndex(initialIndex);
    212             return null;
    213         }
    214 
    215         int w = whole.intValue();
    216         int n = num.intValue();
    217         int d = den.intValue();
    218         return new Fraction(((Math.abs(w) * d) + n) * MathUtils.sign(w), d);
    219     }
    220 
    221     /**
    222      * Modify the whole format.
    223      * @param format The new whole format value.
    224      * @throws NullArgumentException if {@code format} is {@code null}.
    225      */
    226     public void setWholeFormat(NumberFormat format) {
    227         if (format == null) {
    228             throw new NullArgumentException(LocalizedFormats.WHOLE_FORMAT);
    229         }
    230         this.wholeFormat = format;
    231     }
    232 }
    233