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    }