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.math.BigInteger;
20  import java.text.FieldPosition;
21  import java.text.NumberFormat;
22  import java.text.ParsePosition;
23  
24  import org.apache.commons.math.MathRuntimeException;
25  
26  /**
27   * Formats a BigFraction 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 ProperBigFractionFormat extends BigFractionFormat {
38      
39      /** Serializable version identifier */
40      private static final long serialVersionUID = -6337346779577272307L;
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 ProperBigFractionFormat() {
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 ProperBigFractionFormat(final 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 ProperBigFractionFormat(final NumberFormat wholeFormat,
71                                     final NumberFormat numeratorFormat,
72                                     final NumberFormat denominatorFormat) {
73          super(numeratorFormat, denominatorFormat);
74          setWholeFormat(wholeFormat);
75      }
76      
77      /**
78       * Formats a {@link BigFraction} object to produce a string.  The BigFraction
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      @Override
88      public StringBuffer format(final BigFraction fraction,
89                                 final StringBuffer toAppendTo, final FieldPosition pos) {
90          
91          pos.setBeginIndex(0);
92          pos.setEndIndex(0);
93  
94          BigInteger num = fraction.getNumerator();
95          BigInteger den = fraction.getDenominator();
96          BigInteger whole = num.divide(den);
97          num = num.remainder(den);
98          
99          if (!BigInteger.ZERO.equals(whole)) {
100             getWholeFormat().format(whole, toAppendTo, pos);
101             toAppendTo.append(' ');
102             if (num.compareTo(BigInteger.ZERO) < 0) {
103                 num = num.negate();
104             }
105         }
106         getNumeratorFormat().format(num, toAppendTo, pos);
107         toAppendTo.append(" / ");
108         getDenominatorFormat().format(den, toAppendTo, 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 BigFraction} object.  This method
123      * expects the string to be formatted as a proper BigFraction.
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 BigFraction} object.
132      */
133     @Override
134     public BigFraction parse(final String source, final ParsePosition pos) {
135         // try to parse improper BigFraction
136         BigFraction ret = super.parse(source, pos);
137         if (ret != null) {
138             return ret;
139         }
140         
141         final int initialIndex = pos.getIndex();
142 
143         // parse whitespace
144         parseAndIgnoreWhitespace(source, pos);
145 
146         // parse whole
147         BigInteger whole = parseNextBigInteger(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         BigInteger num = parseNextBigInteger(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.compareTo(BigInteger.ZERO) < 0) {
170             // minus signs should be leading, invalid expression
171             pos.setIndex(initialIndex);
172             return null;
173         }
174 
175         // parse '/'
176         final int startIndex = pos.getIndex();
177         final char c = parseNextCharacter(source, pos);
178         switch (c) {
179         case 0 :
180             // no '/'
181             // return num as a BigFraction
182             return new BigFraction(num);
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         final BigInteger den = parseNextBigInteger(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.compareTo(BigInteger.ZERO) < 0) {
209             // minus signs must be leading, invalid
210             pos.setIndex(initialIndex);
211             return null;
212         }
213 
214         boolean wholeIsNeg = whole.compareTo(BigInteger.ZERO) < 0;
215         if (wholeIsNeg) {
216             whole = whole.negate();
217         }
218         num = whole.multiply(den).add(num);
219         if (wholeIsNeg) {
220             num = num.negate();
221         }
222 
223         return new BigFraction(num, den);
224 
225     }
226     
227     /**
228      * Modify the whole format.
229      * @param format The new whole format value.
230      * @throws IllegalArgumentException if <code>format</code> is
231      *         <code>null</code>.
232      */
233     public void setWholeFormat(final NumberFormat format) {
234         if (format == null) {
235             throw MathRuntimeException.createIllegalArgumentException(
236                 "whole format can not be null");
237         }
238         this.wholeFormat = format;
239     }
240 
241 }