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.math3.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.ParsePosition;
025    import java.util.Locale;
026    
027    import org.apache.commons.math3.exception.MathIllegalArgumentException;
028    import org.apache.commons.math3.exception.MathParseException;
029    import 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     */
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 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    }