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