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.io.Serializable;
021    import java.math.BigInteger;
022    import java.text.FieldPosition;
023    import java.text.NumberFormat;
024    import java.text.ParseException;
025    import java.text.ParsePosition;
026    import java.util.Locale;
027    
028    import org.apache.commons.math.MathRuntimeException;
029    import org.apache.commons.math.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 $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 ao??t 2010) $
040     */
041    public 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 IllegalArgumentException is <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 MathRuntimeException.createIllegalArgumentException(
178                    LocalizedFormats.CANNOT_FORMAT_OBJECT_TO_FRACTION);
179            }
180    
181            return ret;
182        }
183    
184        /**
185         * Parses a string to produce a {@link BigFraction} object.
186         * @param source the string to parse
187         * @return the parsed {@link BigFraction} object.
188         * @exception ParseException if the beginning of the specified string
189         *            cannot be parsed.
190         */
191        @Override
192        public BigFraction parse(final String source) throws ParseException {
193            final ParsePosition parsePosition = new ParsePosition(0);
194            final BigFraction result = parse(source, parsePosition);
195            if (parsePosition.getIndex() == 0) {
196                throw MathRuntimeException.createParseException(
197                        parsePosition.getErrorIndex(),
198                        LocalizedFormats.UNPARSEABLE_FRACTION_NUMBER, source);
199            }
200            return result;
201        }
202    
203        /**
204         * Parses a string to produce a {@link BigFraction} object.
205         * This method expects the string to be formatted as an improper BigFraction.
206         * @param source the string to parse
207         * @param pos input/ouput parsing parameter.
208         * @return the parsed {@link BigFraction} object.
209         */
210        @Override
211        public BigFraction parse(final String source, final ParsePosition pos) {
212            final int initialIndex = pos.getIndex();
213    
214            // parse whitespace
215            parseAndIgnoreWhitespace(source, pos);
216    
217            // parse numerator
218            final BigInteger num = parseNextBigInteger(source, pos);
219            if (num == null) {
220                // invalid integer number
221                // set index back to initial, error index should already be set
222                // character examined.
223                pos.setIndex(initialIndex);
224                return null;
225            }
226    
227            // parse '/'
228            final int startIndex = pos.getIndex();
229            final char c = parseNextCharacter(source, pos);
230            switch (c) {
231            case 0 :
232                // no '/'
233                // return num as a BigFraction
234                return new BigFraction(num);
235            case '/' :
236                // found '/', continue parsing denominator
237                break;
238            default :
239                // invalid '/'
240                // set index back to initial, error index should be the last
241                // character examined.
242                pos.setIndex(initialIndex);
243                pos.setErrorIndex(startIndex);
244                return null;
245            }
246    
247            // parse whitespace
248            parseAndIgnoreWhitespace(source, pos);
249    
250            // parse denominator
251            final BigInteger den = parseNextBigInteger(source, pos);
252            if (den == null) {
253                // invalid integer number
254                // set index back to initial, error index should already be set
255                // character examined.
256                pos.setIndex(initialIndex);
257                return null;
258            }
259    
260            return new BigFraction(num, den);
261        }
262    
263        /**
264         * Parses a string to produce a <code>BigInteger</code>.
265         * @param source the string to parse
266         * @param pos input/ouput parsing parameter.
267         * @return a parsed <code>BigInteger</code> or null if string does not
268         * contain a BigInteger at the specified position
269         */
270        protected BigInteger parseNextBigInteger(final String source,
271                                                 final ParsePosition pos) {
272    
273            final int start = pos.getIndex();
274             int end = (source.charAt(start) == '-') ? (start + 1) : start;
275             while((end < source.length()) &&
276                   Character.isDigit(source.charAt(end))) {
277                 ++end;
278             }
279    
280             try {
281                 BigInteger n = new BigInteger(source.substring(start, end));
282                 pos.setIndex(end);
283                 return n;
284             } catch (NumberFormatException nfe) {
285                 pos.setErrorIndex(start);
286                 return null;
287             }
288    
289        }
290    
291    }