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 }