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    
030    /**
031     * Formats a BigFraction number in proper format or improper format.
032     * <p>
033     * The number format for each of the whole number, numerator and,
034     * denominator can be configured.
035     * </p>
036     *
037     * @since 2.0
038     * @version $Revision: 762087 $ $Date: 2009-04-05 10:20:18 -0400 (Sun, 05 Apr 2009) $
039     */
040    public 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 IllegalArgumentException is <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 MathRuntimeException.createIllegalArgumentException(
177                    "cannot format given object as a fraction number");
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 ParseException if the beginning of the specified string
188         *            cannot be parsed.
189         */
190        @Override
191        public BigFraction parse(final String source) throws ParseException {
192            final ParsePosition parsePosition = new ParsePosition(0);
193            final BigFraction result = parse(source, parsePosition);
194            if (parsePosition.getIndex() == 0) {
195                throw MathRuntimeException.createParseException(
196                        parsePosition.getErrorIndex(),
197                        "unparseable fraction number: \"{0}\"", source);
198            }
199            return result;
200        }
201        
202        /**
203         * Parses a string to produce a {@link BigFraction} object.
204         * This method expects the string to be formatted as an improper BigFraction.  
205         * @param source the string to parse
206         * @param pos input/ouput parsing parameter.
207         * @return the parsed {@link BigFraction} object.
208         */
209        @Override
210        public BigFraction parse(final String source, final ParsePosition pos) {
211            final int initialIndex = pos.getIndex();
212    
213            // parse whitespace
214            parseAndIgnoreWhitespace(source, pos);
215    
216            // parse numerator
217            final BigInteger num = parseNextBigInteger(source, pos);
218            if (num == null) {
219                // invalid integer number
220                // set index back to initial, error index should already be set
221                // character examined.
222                pos.setIndex(initialIndex);
223                return null;
224            }
225    
226            // parse '/'
227            final int startIndex = pos.getIndex();
228            final char c = parseNextCharacter(source, pos);
229            switch (c) {
230            case 0 :
231                // no '/'
232                // return num as a BigFraction
233                return new BigFraction(num);
234            case '/' :
235                // found '/', continue parsing denominator
236                break;
237            default :
238                // invalid '/'
239                // set index back to initial, error index should be the last
240                // character examined.
241                pos.setIndex(initialIndex);
242                pos.setErrorIndex(startIndex);
243                return null;
244            }
245    
246            // parse whitespace
247            parseAndIgnoreWhitespace(source, pos);
248    
249            // parse denominator
250            final BigInteger den = parseNextBigInteger(source, pos);
251            if (den == null) {
252                // invalid integer number
253                // set index back to initial, error index should already be set
254                // character examined.
255                pos.setIndex(initialIndex);
256                return null;
257            }
258    
259            return new BigFraction(num, den);
260        }
261    
262        /**
263         * Parses a string to produce a <code>BigInteger</code>.
264         * @param source the string to parse
265         * @param pos input/ouput parsing parameter.
266         * @return a parsed <code>BigInteger</code> or null if string does not
267         * contain a BigInteger at the specified position
268         */
269        protected BigInteger parseNextBigInteger(final String source,
270                                                 final ParsePosition pos) {
271    
272            final int start = pos.getIndex();
273             int end = (source.charAt(start) == '-') ? (start + 1) : start;
274             while((end < source.length()) &&
275                   Character.isDigit(source.charAt(end))) {
276                 ++end;
277             }
278    
279             try {
280                 BigInteger n = new BigInteger(source.substring(start, end));
281                 pos.setIndex(end);
282                 return n;
283             } catch (NumberFormatException nfe) {
284                 pos.setErrorIndex(start);
285                 return null;
286             }
287    
288        }
289    
290    }