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.math3.fraction;
019
020 import java.text.FieldPosition;
021 import java.text.NumberFormat;
022 import java.text.ParsePosition;
023 import java.util.Locale;
024
025 import org.apache.commons.math3.exception.MathIllegalArgumentException;
026 import org.apache.commons.math3.exception.MathParseException;
027 import org.apache.commons.math3.exception.util.LocalizedFormats;
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 $Id: FractionFormat.java 1416643 2012-12-03 19:37:14Z tn $
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 FractionConversionException if the number cannot be converted to a fraction
169 * @throws MathIllegalArgumentException if <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 throws FractionConversionException, MathIllegalArgumentException {
175 StringBuffer ret = null;
176
177 if (obj instanceof Fraction) {
178 ret = format((Fraction) obj, toAppendTo, pos);
179 } else if (obj instanceof Number) {
180 ret = format(new Fraction(((Number) obj).doubleValue()), toAppendTo, pos);
181 } else {
182 throw new MathIllegalArgumentException(LocalizedFormats.CANNOT_FORMAT_OBJECT_TO_FRACTION);
183 }
184
185 return ret;
186 }
187
188 /**
189 * Parses a string to produce a {@link Fraction} object.
190 * @param source the string to parse
191 * @return the parsed {@link Fraction} object.
192 * @exception MathParseException if the beginning of the specified string
193 * cannot be parsed.
194 */
195 @Override
196 public Fraction parse(final String source) throws MathParseException {
197 final ParsePosition parsePosition = new ParsePosition(0);
198 final Fraction result = parse(source, parsePosition);
199 if (parsePosition.getIndex() == 0) {
200 throw new MathParseException(source, parsePosition.getErrorIndex(), Fraction.class);
201 }
202 return result;
203 }
204
205 /**
206 * Parses a string to produce a {@link Fraction} object. This method
207 * expects the string to be formatted as an improper fraction.
208 * @param source the string to parse
209 * @param pos input/output parsing parameter.
210 * @return the parsed {@link Fraction} object.
211 */
212 @Override
213 public Fraction parse(final String source, final ParsePosition pos) {
214 final int initialIndex = pos.getIndex();
215
216 // parse whitespace
217 parseAndIgnoreWhitespace(source, pos);
218
219 // parse numerator
220 final Number num = getNumeratorFormat().parse(source, pos);
221 if (num == null) {
222 // invalid integer number
223 // set index back to initial, error index should already be set
224 // character examined.
225 pos.setIndex(initialIndex);
226 return null;
227 }
228
229 // parse '/'
230 final int startIndex = pos.getIndex();
231 final char c = parseNextCharacter(source, pos);
232 switch (c) {
233 case 0 :
234 // no '/'
235 // return num as a fraction
236 return new Fraction(num.intValue(), 1);
237 case '/' :
238 // found '/', continue parsing denominator
239 break;
240 default :
241 // invalid '/'
242 // set index back to initial, error index should be the last
243 // character examined.
244 pos.setIndex(initialIndex);
245 pos.setErrorIndex(startIndex);
246 return null;
247 }
248
249 // parse whitespace
250 parseAndIgnoreWhitespace(source, pos);
251
252 // parse denominator
253 final Number den = getDenominatorFormat().parse(source, pos);
254 if (den == null) {
255 // invalid integer number
256 // set index back to initial, error index should already be set
257 // character examined.
258 pos.setIndex(initialIndex);
259 return null;
260 }
261
262 return new Fraction(num.intValue(), den.intValue());
263 }
264
265 }