/* | |
* Copyright 2005 The Apache Software Foundation. | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package org.apache.commons.math.fraction; | |
import java.io.Serializable; | |
import java.text.FieldPosition; | |
import java.text.Format; | |
import java.text.NumberFormat; | |
import java.text.ParseException; | |
import java.text.ParsePosition; | |
import java.util.Locale; | |
import org.apache.commons.math.ConvergenceException; | |
/** | |
* Formats a Fraction number in proper format or improper format. The number | |
* format for each of the whole number, numerator and, denominator can be | |
* configured. | |
* | |
* @since 1.1 | |
* @version $Revision$ $Date$ | |
*/ | |
public class FractionFormat extends Format implements Serializable { | |
/** Serializable version identifier */ | |
static final long serialVersionUID = -6337346779577272306L; | |
/** The format used for the denominator. */ | |
private NumberFormat denominatorFormat; | |
/** The format used for the numerator. */ | |
private NumberFormat numeratorFormat; | |
/** | |
* Create an improper formatting instance with the default number format | |
* for the numerator and denominator. | |
*/ | |
public FractionFormat() { | |
this(getDefaultNumberFormat()); | |
} | |
/** | |
* Create an improper formatting instance with a custom number format for | |
* both the numerator and denominator. | |
* @param format the custom format for both the numerator and denominator. | |
*/ | |
public FractionFormat(NumberFormat format) { | |
this(format, (NumberFormat)format.clone()); | |
} | |
/** | |
* Create an improper formatting instance with a custom number format for | |
* the numerator and a custom number format for the denominator. | |
* @param numeratorFormat the custom format for the numerator. | |
* @param denominatorFormat the custom format for the denominator. | |
*/ | |
public FractionFormat(NumberFormat numeratorFormat, | |
NumberFormat denominatorFormat) | |
{ | |
super(); | |
this.numeratorFormat = numeratorFormat; | |
this.denominatorFormat = denominatorFormat; | |
} | |
/** | |
* This static method calls formatFraction() on a default instance of | |
* FractionFormat. | |
* | |
* @param f Fraction object to format | |
* @return A formatted fraction in proper form. | |
*/ | |
public static String formatFraction(Fraction f) { | |
return getImproperInstance().format(f); | |
} | |
/** | |
* Get the set of locales for which complex formats are available. This | |
* is the same set as the {@link NumberFormat} set. | |
* @return available complex format locales. | |
*/ | |
public static Locale[] getAvailableLocales() { | |
return NumberFormat.getAvailableLocales(); | |
} | |
/** | |
* Returns the default complex format for the current locale. | |
* @return the default complex format. | |
*/ | |
public static FractionFormat getImproperInstance() { | |
return getImproperInstance(Locale.getDefault()); | |
} | |
/** | |
* Returns the default complex format for the given locale. | |
* @param locale the specific locale used by the format. | |
* @return the complex format specific to the given locale. | |
*/ | |
public static FractionFormat getImproperInstance(Locale locale) { | |
NumberFormat f = getDefaultNumberFormat(locale); | |
return new FractionFormat(f); | |
} | |
/** | |
* Returns the default complex format for the current locale. | |
* @return the default complex format. | |
*/ | |
public static FractionFormat getProperInstance() { | |
return getProperInstance(Locale.getDefault()); | |
} | |
/** | |
* Returns the default complex format for the given locale. | |
* @param locale the specific locale used by the format. | |
* @return the complex format specific to the given locale. | |
*/ | |
public static FractionFormat getProperInstance(Locale locale) { | |
NumberFormat f = getDefaultNumberFormat(locale); | |
return new ProperFractionFormat(f); | |
} | |
/** | |
* Create a default number format. The default number format is based on | |
* {@link NumberFormat#getNumberInstance(java.util.Locale)} with the only | |
* customizing is the maximum number of fraction digits, which is set to 0. | |
* @return the default number format. | |
*/ | |
protected static NumberFormat getDefaultNumberFormat() { | |
return getDefaultNumberFormat(Locale.getDefault()); | |
} | |
/** | |
* Create a default number format. The default number format is based on | |
* {@link NumberFormat#getNumberInstance(java.util.Locale)} with the only | |
* customizing is the maximum number of fraction digits, which is set to 0. | |
* @param locale the specific locale used by the format. | |
* @return the default number format specific to the given locale. | |
*/ | |
private static NumberFormat getDefaultNumberFormat(Locale locale) { | |
NumberFormat nf = NumberFormat.getNumberInstance(locale); | |
nf.setMaximumFractionDigits(0); | |
nf.setParseIntegerOnly(true); | |
return nf; | |
} | |
/** | |
* Formats a {@link Fraction} object to produce a string. The fraction is | |
* output in improper format. | |
* | |
* @param fraction the object to format. | |
* @param toAppendTo where the text is to be appended | |
* @param pos On input: an alignment field, if desired. On output: the | |
* offsets of the alignment field | |
* @return the value passed in as toAppendTo. | |
*/ | |
public StringBuffer format(Fraction fraction, StringBuffer toAppendTo, | |
FieldPosition pos) { | |
pos.setBeginIndex(0); | |
pos.setEndIndex(0); | |
getNumeratorFormat().format(fraction.getNumerator(), toAppendTo, pos); | |
toAppendTo.append(" / "); | |
getDenominatorFormat().format(fraction.getDenominator(), toAppendTo, | |
pos); | |
return toAppendTo; | |
} | |
/** | |
* Formats a object to produce a string. <code>obj</code> must be either a | |
* {@link Fraction} object or a {@link Number} object. Any other type of | |
* object will result in an {@link IllegalArgumentException} being thrown. | |
* | |
* @param obj the object to format. | |
* @param toAppendTo where the text is to be appended | |
* @param pos On input: an alignment field, if desired. On output: the | |
* offsets of the alignment field | |
* @return the value passed in as toAppendTo. | |
* @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition) | |
* @throws IllegalArgumentException is <code>obj</code> is not a valid type. | |
*/ | |
public StringBuffer format(Object obj, StringBuffer toAppendTo, | |
FieldPosition pos) | |
{ | |
StringBuffer ret = null; | |
if (obj instanceof Fraction) { | |
ret = format( (Fraction)obj, toAppendTo, pos); | |
} else if (obj instanceof Number) { | |
try { | |
ret = format( new Fraction(((Number)obj).doubleValue()), | |
toAppendTo, pos); | |
} catch (ConvergenceException ex) { | |
throw new IllegalArgumentException( | |
"Cannot convert given object to a fraction."); | |
} | |
} else { | |
throw new IllegalArgumentException( | |
"Cannot format given object as a fraction"); | |
} | |
return ret; | |
} | |
/** | |
* Access the denominator format. | |
* @return the denominator format. | |
*/ | |
public NumberFormat getDenominatorFormat() { | |
return denominatorFormat; | |
} | |
/** | |
* Access the numerator format. | |
* @return the numerator format. | |
*/ | |
public NumberFormat getNumeratorFormat() { | |
return numeratorFormat; | |
} | |
/** | |
* Parses a string to produce a {@link Fraction} object. | |
* @param source the string to parse | |
* @return the parsed {@link Fraction} object. | |
* @exception ParseException if the beginning of the specified string | |
* cannot be parsed. | |
*/ | |
public Fraction parse(String source) throws ParseException { | |
ParsePosition parsePosition = new ParsePosition(0); | |
Fraction result = parse(source, parsePosition); | |
if (parsePosition.getIndex() == 0) { | |
throw new ParseException("Unparseable fraction number: \"" + | |
source + "\"", parsePosition.getErrorIndex()); | |
} | |
return result; | |
} | |
/** | |
* Parses a string to produce a {@link Fraction} object. This method | |
* expects the string to be formatted as an improper fraction. | |
* @param source the string to parse | |
* @param pos input/ouput parsing parameter. | |
* @return the parsed {@link Fraction} object. | |
*/ | |
public Fraction parse(String source, ParsePosition pos) { | |
int initialIndex = pos.getIndex(); | |
// parse whitespace | |
parseAndIgnoreWhitespace(source, pos); | |
// parse numerator | |
Number num = getNumeratorFormat().parse(source, pos); | |
if (num == null) { | |
// invalid integer number | |
// set index back to initial, error index should already be set | |
// character examined. | |
pos.setIndex(initialIndex); | |
return null; | |
} | |
// parse '/' | |
int startIndex = pos.getIndex(); | |
char c = parseNextCharacter(source, pos); | |
switch (c) { | |
case 0 : | |
// no '/' | |
// return num as a fraction | |
return new Fraction(num.intValue(), 1); | |
case '/' : | |
// found '/', continue parsing denominator | |
break; | |
default : | |
// invalid '/' | |
// set index back to initial, error index should be the last | |
// character examined. | |
pos.setIndex(initialIndex); | |
pos.setErrorIndex(startIndex); | |
return null; | |
} | |
// parse whitespace | |
parseAndIgnoreWhitespace(source, pos); | |
// parse denominator | |
Number den = getDenominatorFormat().parse(source, pos); | |
if (den == null) { | |
// invalid integer number | |
// set index back to initial, error index should already be set | |
// character examined. | |
pos.setIndex(initialIndex); | |
return null; | |
} | |
return new Fraction(num.intValue(), den.intValue()); | |
} | |
/** | |
* Parses a string to produce a object. | |
* @param source the string to parse | |
* @param pos input/ouput parsing parameter. | |
* @return the parsed object. | |
* @see java.text.Format#parseObject(java.lang.String, java.text.ParsePosition) | |
*/ | |
public Object parseObject(String source, ParsePosition pos) { | |
return parse(source, pos); | |
} | |
/** | |
* Modify the denominator format. | |
* @param format the new denominator format value. | |
* @throws IllegalArgumentException if <code>format</code> is | |
* <code>null</code>. | |
*/ | |
public void setDenominatorFormat(NumberFormat format) { | |
if (format == null) { | |
throw new IllegalArgumentException( | |
"denominator format can not be null."); | |
} | |
this.denominatorFormat = format; | |
} | |
/** | |
* Modify the numerator format. | |
* @param format the new numerator format value. | |
* @throws IllegalArgumentException if <code>format</code> is | |
* <code>null</code>. | |
*/ | |
public void setNumeratorFormat(NumberFormat format) { | |
if (format == null) { | |
throw new IllegalArgumentException( | |
"numerator format can not be null."); | |
} | |
this.numeratorFormat = format; | |
} | |
/** | |
* Parses <code>source</code> until a non-whitespace character is found. | |
* @param source the string to parse | |
* @param pos input/ouput parsing parameter. On output, <code>pos</code> | |
* holds the index of the next non-whitespace character. | |
*/ | |
protected static void parseAndIgnoreWhitespace( | |
String source, ParsePosition pos) | |
{ | |
parseNextCharacter(source, pos); | |
pos.setIndex(pos.getIndex() - 1); | |
} | |
/** | |
* Parses <code>source</code> until a non-whitespace character is found. | |
* @param source the string to parse | |
* @param pos input/ouput parsing parameter. | |
* @return the first non-whitespace character. | |
*/ | |
protected static char parseNextCharacter(String source, ParsePosition pos) { | |
int index = pos.getIndex(); | |
int n = source.length(); | |
char ret = 0; | |
if (index < n) { | |
char c; | |
do { | |
c = source.charAt(index++); | |
} while (Character.isWhitespace(c) && index < n); | |
pos.setIndex(index); | |
if (index < n) { | |
ret = c; | |
} | |
} | |
return ret; | |
} | |
} |