001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 package org.apache.commons.math.fraction;
019
020 import java.text.FieldPosition;
021 import java.text.NumberFormat;
022 import java.text.ParseException;
023 import java.text.ParsePosition;
024 import java.util.Locale;
025
026 import org.apache.commons.math.ConvergenceException;
027 import org.apache.commons.math.MathRuntimeException;
028
029 /**
030 * Formats a Fraction number in proper format or improper format. The number
031 * format for each of the whole number, numerator and, denominator can be
032 * configured.
033 *
034 * @since 1.1
035 * @version $Revision: 811685 $ $Date: 2009-09-05 13:36:48 -0400 (Sat, 05 Sep 2009) $
036 */
037 public class FractionFormat extends AbstractFormat {
038
039 /** Serializable version identifier */
040 private static final long serialVersionUID = 3008655719530972611L;
041
042 /**
043 * Create an improper formatting instance with the default number format
044 * for the numerator and denominator.
045 */
046 public FractionFormat() {
047 }
048
049 /**
050 * Create an improper formatting instance with a custom number format for
051 * both the numerator and denominator.
052 * @param format the custom format for both the numerator and denominator.
053 */
054 public FractionFormat(final NumberFormat format) {
055 super(format);
056 }
057
058 /**
059 * Create an improper formatting instance with a custom number format for
060 * the numerator and a custom number format for the denominator.
061 * @param numeratorFormat the custom format for the numerator.
062 * @param denominatorFormat the custom format for the denominator.
063 */
064 public FractionFormat(final NumberFormat numeratorFormat,
065 final NumberFormat denominatorFormat) {
066 super(numeratorFormat, denominatorFormat);
067 }
068
069 /**
070 * Get the set of locales for which complex formats are available. This
071 * is the same set as the {@link NumberFormat} set.
072 * @return available complex format locales.
073 */
074 public static Locale[] getAvailableLocales() {
075 return NumberFormat.getAvailableLocales();
076 }
077
078 /**
079 * This static method calls formatFraction() on a default instance of
080 * FractionFormat.
081 *
082 * @param f Fraction object to format
083 * @return A formatted fraction in proper form.
084 */
085 public static String formatFraction(Fraction f) {
086 return getImproperInstance().format(f);
087 }
088
089 /**
090 * Returns the default complex format for the current locale.
091 * @return the default complex format.
092 */
093 public static FractionFormat getImproperInstance() {
094 return getImproperInstance(Locale.getDefault());
095 }
096
097 /**
098 * Returns the default complex format for the given locale.
099 * @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 }