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