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