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