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  
18  package org.apache.commons.math.fraction;
19  
20  import java.text.FieldPosition;
21  import java.text.NumberFormat;
22  import java.text.ParseException;
23  import java.text.ParsePosition;
24  import java.util.Locale;
25  
26  import org.apache.commons.math.ConvergenceException;
27  import org.apache.commons.math.MathRuntimeException;
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: 762087 $ $Date: 2009-04-05 10:20:18 -0400 (Sun, 05 Apr 2009) $
36   */
37  public class FractionFormat extends AbstractFormat {
38      
39      /** Serializable version identifier */
40      private static final long serialVersionUID = 3008655719530972611L;
41  
42      /**
43       * Create an improper formatting instance with the default number format
44       * for the numerator and denominator.  
45       */
46      public FractionFormat() {
47      }
48  
49      /**
50       * Create an improper formatting instance with a custom number format for
51       * both the numerator and denominator.
52       * @param format the custom format for both the numerator and denominator.
53       */
54      public FractionFormat(final NumberFormat format) {
55          super(format);
56      }
57  
58      /**
59       * Create an improper formatting instance with a custom number format for
60       * the numerator and a custom number format for the denominator.
61       * @param numeratorFormat the custom format for the numerator.
62       * @param denominatorFormat the custom format for the denominator.
63       */
64      public FractionFormat(final NumberFormat numeratorFormat,
65                            final NumberFormat denominatorFormat) {
66          super(numeratorFormat, denominatorFormat);
67      }
68  
69      /**
70       * Get the set of locales for which complex formats are available.  This
71       * is the same set as the {@link NumberFormat} set. 
72       * @return available complex format locales.
73       */
74      public static Locale[] getAvailableLocales() {
75          return NumberFormat.getAvailableLocales();
76      }
77  
78      /**
79       * This static method calls formatFraction() on a default instance of
80       * FractionFormat.
81       *
82       * @param f Fraction object to format
83       * @return A formatted fraction in proper form.
84       */
85      public static String formatFraction(Fraction f) {
86          return getImproperInstance().format(f);
87      }
88      
89      /**
90       * Returns the default complex format for the current locale.
91       * @return the default complex format.
92       */
93      public static FractionFormat getImproperInstance() {
94          return getImproperInstance(Locale.getDefault());
95      }
96      
97      /**
98       * Returns the default complex format for the given locale.
99       * @param locale the specific locale used by the format.
100      * @return the complex format specific to the given locale.
101      */
102     public static FractionFormat getImproperInstance(final Locale locale) {
103         return new FractionFormat(getDefaultNumberFormat(locale));
104     }
105     
106     /**
107      * Returns the default complex format for the current locale.
108      * @return the default complex format.
109      */
110     public static FractionFormat getProperInstance() {
111         return getProperInstance(Locale.getDefault());
112     }
113     
114     /**
115      * Returns the default complex format for the given locale.
116      * @param locale the specific locale used by the format.
117      * @return the complex format specific to the given locale.
118      */
119     public static FractionFormat getProperInstance(final Locale locale) {
120         return new ProperFractionFormat(getDefaultNumberFormat(locale));
121     }
122     
123     /**
124      * Create a default number format.  The default number format is based on
125      * {@link NumberFormat#getNumberInstance(java.util.Locale)} with the only
126      * customizing is the maximum number of fraction digits, which is set to 0.  
127      * @return the default number format.
128      */
129     protected static NumberFormat getDefaultNumberFormat() {
130         return getDefaultNumberFormat(Locale.getDefault());
131     }
132     
133     /**
134      * Formats a {@link Fraction} object to produce a string.  The fraction is
135      * output in improper format.
136      *
137      * @param fraction the object to format.
138      * @param toAppendTo where the text is to be appended
139      * @param pos On input: an alignment field, if desired. On output: the
140      *            offsets of the alignment field
141      * @return the value passed in as toAppendTo.
142      */
143     public StringBuffer format(final Fraction fraction,
144                                final StringBuffer toAppendTo, final FieldPosition pos) {
145         
146         pos.setBeginIndex(0);
147         pos.setEndIndex(0);
148 
149         getNumeratorFormat().format(fraction.getNumerator(), toAppendTo, pos);
150         toAppendTo.append(" / ");
151         getDenominatorFormat().format(fraction.getDenominator(), toAppendTo,
152             pos);
153         
154         return toAppendTo;
155     }
156     
157     /**
158      * Formats an object and appends the result to a StringBuffer. <code>obj</code> must be either a 
159      * {@link Fraction} object or a {@link Number} object.  Any other type of
160      * object will result in an {@link IllegalArgumentException} being thrown.
161      *
162      * @param obj the object to format.
163      * @param toAppendTo where the text is to be appended
164      * @param pos On input: an alignment field, if desired. On output: the
165      *            offsets of the alignment field
166      * @return the value passed in as toAppendTo.
167      * @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition)
168      * @throws IllegalArgumentException is <code>obj</code> is not a valid type.
169      */
170     @Override
171     public StringBuffer format(final Object obj,
172                                final StringBuffer toAppendTo, final FieldPosition pos) {
173         StringBuffer ret = null;
174         
175         if (obj instanceof Fraction) {
176             ret = format((Fraction) obj, toAppendTo, pos);
177         } else if (obj instanceof Number) {
178             try {
179                 ret = format(new Fraction(((Number) obj).doubleValue()),
180                              toAppendTo, pos);
181             } catch (ConvergenceException ex) {
182                 throw MathRuntimeException.createIllegalArgumentException(
183                     "cannot convert given object to a fraction number: {0}",
184                     ex.getLocalizedMessage());
185             }
186         } else { 
187             throw MathRuntimeException.createIllegalArgumentException(
188                 "cannot format given object as a fraction number");
189         }
190         
191         return ret;
192     }
193 
194     /**
195      * Parses a string to produce a {@link Fraction} object.
196      * @param source the string to parse
197      * @return the parsed {@link Fraction} object.
198      * @exception ParseException if the beginning of the specified string
199      *            cannot be parsed.
200      */
201     @Override
202     public Fraction parse(final String source) throws ParseException {
203         final ParsePosition parsePosition = new ParsePosition(0);
204         final Fraction result = parse(source, parsePosition);
205         if (parsePosition.getIndex() == 0) {
206             throw MathRuntimeException.createParseException(
207                     parsePosition.getErrorIndex(),
208                     "unparseable fraction number: \"{0}\"", source);
209         }
210         return result;
211     }
212     
213     /**
214      * Parses a string to produce a {@link Fraction} object.  This method
215      * expects the string to be formatted as an improper fraction.  
216      * @param source the string to parse
217      * @param pos input/ouput parsing parameter.
218      * @return the parsed {@link Fraction} object.
219      */
220     @Override
221     public Fraction parse(final String source, final ParsePosition pos) {
222         final int initialIndex = pos.getIndex();
223 
224         // parse whitespace
225         parseAndIgnoreWhitespace(source, pos);
226 
227         // parse numerator
228         final Number num = getNumeratorFormat().parse(source, pos);
229         if (num == null) {
230             // invalid integer number
231             // set index back to initial, error index should already be set
232             // character examined.
233             pos.setIndex(initialIndex);
234             return null;
235         }
236 
237         // parse '/'
238         final int startIndex = pos.getIndex();
239         final char c = parseNextCharacter(source, pos);
240         switch (c) {
241         case 0 :
242             // no '/'
243             // return num as a fraction
244             return new Fraction(num.intValue(), 1);
245         case '/' :
246             // found '/', continue parsing denominator
247             break;
248         default :
249             // invalid '/'
250             // set index back to initial, error index should be the last
251             // character examined.
252             pos.setIndex(initialIndex);
253             pos.setErrorIndex(startIndex);
254             return null;
255         }
256 
257         // parse whitespace
258         parseAndIgnoreWhitespace(source, pos);
259 
260         // parse denominator
261         final Number den = getDenominatorFormat().parse(source, pos);
262         if (den == null) {
263             // invalid integer number
264             // set index back to initial, error index should already be set
265             // character examined.
266             pos.setIndex(initialIndex);
267             return null;
268         }
269 
270         return new Fraction(num.intValue(), den.intValue());
271     }
272     
273 }