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 import org.apache.commons.math.exception.util.LocalizedFormats;
029
030 /**
031 * Formats a Fraction number in proper format or improper format. The number
032 * format for each of the whole number, numerator and, denominator can be
033 * configured.
034 *
035 * @since 1.1
036 * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 ao??t 2010) $
037 */
038 public class FractionFormat extends AbstractFormat {
039
040 /** Serializable version identifier */
041 private static final long serialVersionUID = 3008655719530972611L;
042
043 /**
044 * Create an improper formatting instance with the default number format
045 * for the numerator and denominator.
046 */
047 public FractionFormat() {
048 }
049
050 /**
051 * Create an improper formatting instance with a custom number format for
052 * both the numerator and denominator.
053 * @param format the custom format for both the numerator and denominator.
054 */
055 public FractionFormat(final NumberFormat format) {
056 super(format);
057 }
058
059 /**
060 * Create an improper formatting instance with a custom number format for
061 * the numerator and a custom number format for the denominator.
062 * @param numeratorFormat the custom format for the numerator.
063 * @param denominatorFormat the custom format for the denominator.
064 */
065 public FractionFormat(final NumberFormat numeratorFormat,
066 final NumberFormat denominatorFormat) {
067 super(numeratorFormat, denominatorFormat);
068 }
069
070 /**
071 * Get the set of locales for which complex formats are available. This
072 * is the same set as the {@link NumberFormat} set.
073 * @return available complex format locales.
074 */
075 public static Locale[] getAvailableLocales() {
076 return NumberFormat.getAvailableLocales();
077 }
078
079 /**
080 * This static method calls formatFraction() on a default instance of
081 * FractionFormat.
082 *
083 * @param f Fraction object to format
084 * @return A formatted fraction in proper form.
085 */
086 public static String formatFraction(Fraction f) {
087 return getImproperInstance().format(f);
088 }
089
090 /**
091 * Returns the default complex format for the current locale.
092 * @return the default complex format.
093 */
094 public static FractionFormat getImproperInstance() {
095 return getImproperInstance(Locale.getDefault());
096 }
097
098 /**
099 * Returns the default complex format for the given locale.
100 * @param locale the specific locale used by the format.
101 * @return the complex format specific to the given locale.
102 */
103 public static FractionFormat getImproperInstance(final Locale locale) {
104 return new FractionFormat(getDefaultNumberFormat(locale));
105 }
106
107 /**
108 * Returns the default complex format for the current locale.
109 * @return the default complex format.
110 */
111 public static FractionFormat getProperInstance() {
112 return getProperInstance(Locale.getDefault());
113 }
114
115 /**
116 * Returns the default complex format for the given locale.
117 * @param locale the specific locale used by the format.
118 * @return the complex format specific to the given locale.
119 */
120 public static FractionFormat getProperInstance(final Locale locale) {
121 return new ProperFractionFormat(getDefaultNumberFormat(locale));
122 }
123
124 /**
125 * Create a default number format. The default number format is based on
126 * {@link NumberFormat#getNumberInstance(java.util.Locale)} with the only
127 * customizing is the maximum number of fraction digits, which is set to 0.
128 * @return the default number format.
129 */
130 protected static NumberFormat getDefaultNumberFormat() {
131 return getDefaultNumberFormat(Locale.getDefault());
132 }
133
134 /**
135 * Formats a {@link Fraction} object to produce a string. The fraction is
136 * output in improper format.
137 *
138 * @param fraction the object to format.
139 * @param toAppendTo where the text is to be appended
140 * @param pos On input: an alignment field, if desired. On output: the
141 * offsets of the alignment field
142 * @return the value passed in as toAppendTo.
143 */
144 public StringBuffer format(final Fraction fraction,
145 final StringBuffer toAppendTo, final FieldPosition pos) {
146
147 pos.setBeginIndex(0);
148 pos.setEndIndex(0);
149
150 getNumeratorFormat().format(fraction.getNumerator(), toAppendTo, pos);
151 toAppendTo.append(" / ");
152 getDenominatorFormat().format(fraction.getDenominator(), toAppendTo,
153 pos);
154
155 return toAppendTo;
156 }
157
158 /**
159 * Formats an object and appends the result to a StringBuffer. <code>obj</code> must be either a
160 * {@link Fraction} object or a {@link Number} object. Any other type of
161 * object will result in an {@link IllegalArgumentException} being thrown.
162 *
163 * @param obj the object to format.
164 * @param toAppendTo where the text is to be appended
165 * @param pos On input: an alignment field, if desired. On output: the
166 * offsets of the alignment field
167 * @return the value passed in as toAppendTo.
168 * @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition)
169 * @throws IllegalArgumentException is <code>obj</code> is not a valid type.
170 */
171 @Override
172 public StringBuffer format(final Object obj,
173 final StringBuffer toAppendTo, final FieldPosition pos) {
174 StringBuffer ret = null;
175
176 if (obj instanceof Fraction) {
177 ret = format((Fraction) obj, toAppendTo, pos);
178 } else if (obj instanceof Number) {
179 try {
180 ret = format(new Fraction(((Number) obj).doubleValue()),
181 toAppendTo, pos);
182 } catch (ConvergenceException ex) {
183 throw MathRuntimeException.createIllegalArgumentException(
184 LocalizedFormats.CANNOT_CONVERT_OBJECT_TO_FRACTION,
185 ex.getLocalizedMessage());
186 }
187 } else {
188 throw MathRuntimeException.createIllegalArgumentException(
189 LocalizedFormats.CANNOT_FORMAT_OBJECT_TO_FRACTION);
190 }
191
192 return ret;
193 }
194
195 /**
196 * Parses a string to produce a {@link Fraction} object.
197 * @param source the string to parse
198 * @return the parsed {@link Fraction} object.
199 * @exception ParseException if the beginning of the specified string
200 * cannot be parsed.
201 */
202 @Override
203 public Fraction parse(final String source) throws ParseException {
204 final ParsePosition parsePosition = new ParsePosition(0);
205 final Fraction result = parse(source, parsePosition);
206 if (parsePosition.getIndex() == 0) {
207 throw MathRuntimeException.createParseException(
208 parsePosition.getErrorIndex(),
209 LocalizedFormats.UNPARSEABLE_FRACTION_NUMBER, source);
210 }
211 return result;
212 }
213
214 /**
215 * Parses a string to produce a {@link Fraction} object. This method
216 * expects the string to be formatted as an improper fraction.
217 * @param source the string to parse
218 * @param pos input/ouput parsing parameter.
219 * @return the parsed {@link Fraction} object.
220 */
221 @Override
222 public Fraction parse(final String source, final ParsePosition pos) {
223 final int initialIndex = pos.getIndex();
224
225 // parse whitespace
226 parseAndIgnoreWhitespace(source, pos);
227
228 // parse numerator
229 final Number num = getNumeratorFormat().parse(source, pos);
230 if (num == null) {
231 // invalid integer number
232 // set index back to initial, error index should already be set
233 // character examined.
234 pos.setIndex(initialIndex);
235 return null;
236 }
237
238 // parse '/'
239 final int startIndex = pos.getIndex();
240 final char c = parseNextCharacter(source, pos);
241 switch (c) {
242 case 0 :
243 // no '/'
244 // return num as a fraction
245 return new Fraction(num.intValue(), 1);
246 case '/' :
247 // found '/', continue parsing denominator
248 break;
249 default :
250 // invalid '/'
251 // set index back to initial, error index should be the last
252 // character examined.
253 pos.setIndex(initialIndex);
254 pos.setErrorIndex(startIndex);
255 return null;
256 }
257
258 // parse whitespace
259 parseAndIgnoreWhitespace(source, pos);
260
261 // parse denominator
262 final Number den = getDenominatorFormat().parse(source, pos);
263 if (den == null) {
264 // invalid integer number
265 // set index back to initial, error index should already be set
266 // character examined.
267 pos.setIndex(initialIndex);
268 return null;
269 }
270
271 return new Fraction(num.intValue(), den.intValue());
272 }
273
274 }