View Javadoc

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.MathRuntimeException;
24  import org.apache.commons.math.util.MathUtils;
25  
26  /**
27   * Formats a Fraction number in proper format.  The number format for each of
28   * the whole number, numerator and, denominator can be configured.
29   * <p>
30   * Minus signs are only allowed in the whole number part - i.e.,
31   * "-3 1/2" is legitimate and denotes -7/2, but "-3 -1/2" is invalid and
32   * will result in a <code>ParseException</code>.</p>
33   * 
34   * @since 1.1
35   * @version $Revision: 762087 $ $Date: 2009-04-05 10:20:18 -0400 (Sun, 05 Apr 2009) $
36   */
37  public class ProperFractionFormat extends FractionFormat {
38      
39      /** Serializable version identifier */
40      private static final long serialVersionUID = 760934726031766749L;
41  
42      /** The format used for the whole number. */
43      private NumberFormat wholeFormat;
44  
45      /**
46       * Create a proper formatting instance with the default number format for
47       * the whole, numerator, and denominator.  
48       */
49      public ProperFractionFormat() {
50          this(getDefaultNumberFormat());
51      }
52      
53      /**
54       * Create a proper formatting instance with a custom number format for the
55       * whole, numerator, and denominator.
56       * @param format the custom format for the whole, numerator, and
57       *        denominator.
58       */
59      public ProperFractionFormat(NumberFormat format) {
60          this(format, (NumberFormat)format.clone(), (NumberFormat)format.clone());
61      }
62      
63      /**
64       * Create a proper formatting instance with a custom number format for each
65       * of the whole, numerator, and denominator.
66       * @param wholeFormat the custom format for the whole.
67       * @param numeratorFormat the custom format for the numerator.
68       * @param denominatorFormat the custom format for the denominator.
69       */
70      public ProperFractionFormat(NumberFormat wholeFormat,
71              NumberFormat numeratorFormat,
72              NumberFormat denominatorFormat)
73      {
74          super(numeratorFormat, denominatorFormat);
75          setWholeFormat(wholeFormat);
76      }
77      
78      /**
79       * Formats a {@link Fraction} object to produce a string.  The fraction
80       * is output in proper format.
81       *
82       * @param fraction the object to format.
83       * @param toAppendTo where the text is to be appended
84       * @param pos On input: an alignment field, if desired. On output: the
85       *            offsets of the alignment field
86       * @return the value passed in as toAppendTo.
87       */
88      @Override
89      public StringBuffer format(Fraction fraction, StringBuffer toAppendTo,
90              FieldPosition pos) {
91          
92          pos.setBeginIndex(0);
93          pos.setEndIndex(0);
94  
95          int num = fraction.getNumerator();
96          int den = fraction.getDenominator();
97          int whole = num / den;
98          num = num % den;
99          
100         if (whole != 0) {
101             getWholeFormat().format(whole, toAppendTo, pos);
102             toAppendTo.append(' ');
103             num = Math.abs(num);
104         }
105         getNumeratorFormat().format(num, toAppendTo, pos);
106         toAppendTo.append(" / ");
107         getDenominatorFormat().format(den, toAppendTo,
108             pos);
109         
110         return toAppendTo;
111     }
112 
113     /**
114      * Access the whole format.
115      * @return the whole format.
116      */
117     public NumberFormat getWholeFormat() {
118         return wholeFormat;
119     }
120     
121     /**
122      * Parses a string to produce a {@link Fraction} object.  This method
123      * expects the string to be formatted as a proper fraction.
124      * <p>
125      * Minus signs are only allowed in the whole number part - i.e.,
126      * "-3 1/2" is legitimate and denotes -7/2, but "-3 -1/2" is invalid and
127      * will result in a <code>ParseException</code>.</p>
128      * 
129      * @param source the string to parse
130      * @param pos input/ouput parsing parameter.
131      * @return the parsed {@link Fraction} object.
132      */
133     @Override
134     public Fraction parse(String source, ParsePosition pos) {
135         // try to parse improper fraction
136         Fraction ret = super.parse(source, pos);
137         if (ret != null) {
138             return ret;
139         }
140         
141         int initialIndex = pos.getIndex();
142 
143         // parse whitespace
144         parseAndIgnoreWhitespace(source, pos);
145 
146         // parse whole
147         Number whole = getWholeFormat().parse(source, pos);
148         if (whole == null) {
149             // invalid integer number
150             // set index back to initial, error index should already be set
151             // character examined.
152             pos.setIndex(initialIndex);
153             return null;
154         }
155 
156         // parse whitespace
157         parseAndIgnoreWhitespace(source, pos);
158         
159         // parse numerator
160         Number num = getNumeratorFormat().parse(source, pos);
161         if (num == null) {
162             // invalid integer number
163             // set index back to initial, error index should already be set
164             // character examined.
165             pos.setIndex(initialIndex);
166             return null;
167         }
168         
169         if (num.intValue() < 0) {
170             // minus signs should be leading, invalid expression
171             pos.setIndex(initialIndex);
172             return null;
173         }
174 
175         // parse '/'
176         int startIndex = pos.getIndex();
177         char c = parseNextCharacter(source, pos);
178         switch (c) {
179         case 0 :
180             // no '/'
181             // return num as a fraction
182             return new Fraction(num.intValue(), 1);
183         case '/' :
184             // found '/', continue parsing denominator
185             break;
186         default :
187             // invalid '/'
188             // set index back to initial, error index should be the last
189             // character examined.
190             pos.setIndex(initialIndex);
191             pos.setErrorIndex(startIndex);
192             return null;
193         }
194 
195         // parse whitespace
196         parseAndIgnoreWhitespace(source, pos);
197 
198         // parse denominator
199         Number den = getDenominatorFormat().parse(source, pos);
200         if (den == null) {
201             // invalid integer number
202             // set index back to initial, error index should already be set
203             // character examined.
204             pos.setIndex(initialIndex);
205             return null;
206         }
207         
208         if (den.intValue() < 0) {
209             // minus signs must be leading, invalid
210             pos.setIndex(initialIndex);
211             return null;
212         }
213 
214         int w = whole.intValue();
215         int n = num.intValue();
216         int d = den.intValue();
217         return new Fraction(((Math.abs(w) * d) + n) * MathUtils.sign(w), d);
218     }
219     
220     /**
221      * Modify the whole format.
222      * @param format The new whole format value.
223      * @throws IllegalArgumentException if <code>format</code> is
224      *         <code>null</code>.
225      */
226     public void setWholeFormat(NumberFormat format) {
227         if (format == null) {
228             throw MathRuntimeException.createIllegalArgumentException(
229                 "whole format can not be null");
230         }
231         this.wholeFormat = format;
232     }
233 }