1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package org.apache.commons.math.fraction; 19 20 import java.text.FieldPosition; 21 import java.text.NumberFormat; 22 import java.text.ParseException; 23 import java.text.ParsePosition; 24 import java.util.Locale; 25 26 import org.apache.commons.math.ConvergenceException; 27 import org.apache.commons.math.MathRuntimeException; 28 29 /** 30 * Formats a Fraction number in proper format or improper format. The number 31 * format for each of the whole number, numerator and, denominator can be 32 * configured. 33 * 34 * @since 1.1 35 * @version $Revision: 762087 $ $Date: 2009-04-05 10:20:18 -0400 (Sun, 05 Apr 2009) $ 36 */ 37 public class FractionFormat extends AbstractFormat { 38 39 /** Serializable version identifier */ 40 private static final long serialVersionUID = 3008655719530972611L; 41 42 /** 43 * Create an improper formatting instance with the default number format 44 * for the numerator and denominator. 45 */ 46 public FractionFormat() { 47 } 48 49 /** 50 * Create an improper formatting instance with a custom number format for 51 * both the numerator and denominator. 52 * @param format the custom format for both the numerator and denominator. 53 */ 54 public FractionFormat(final NumberFormat format) { 55 super(format); 56 } 57 58 /** 59 * Create an improper formatting instance with a custom number format for 60 * the numerator and a custom number format for the denominator. 61 * @param numeratorFormat the custom format for the numerator. 62 * @param denominatorFormat the custom format for the denominator. 63 */ 64 public FractionFormat(final NumberFormat numeratorFormat, 65 final NumberFormat denominatorFormat) { 66 super(numeratorFormat, denominatorFormat); 67 } 68 69 /** 70 * Get the set of locales for which complex formats are available. This 71 * is the same set as the {@link NumberFormat} set. 72 * @return available complex format locales. 73 */ 74 public static Locale[] getAvailableLocales() { 75 return NumberFormat.getAvailableLocales(); 76 } 77 78 /** 79 * This static method calls formatFraction() on a default instance of 80 * FractionFormat. 81 * 82 * @param f Fraction object to format 83 * @return A formatted fraction in proper form. 84 */ 85 public static String formatFraction(Fraction f) { 86 return getImproperInstance().format(f); 87 } 88 89 /** 90 * Returns the default complex format for the current locale. 91 * @return the default complex format. 92 */ 93 public static FractionFormat getImproperInstance() { 94 return getImproperInstance(Locale.getDefault()); 95 } 96 97 /** 98 * Returns the default complex format for the given locale. 99 * @param locale the specific locale used by the format. 100 * @return the complex format specific to the given locale. 101 */ 102 public static FractionFormat getImproperInstance(final Locale locale) { 103 return new FractionFormat(getDefaultNumberFormat(locale)); 104 } 105 106 /** 107 * Returns the default complex format for the current locale. 108 * @return the default complex format. 109 */ 110 public static FractionFormat getProperInstance() { 111 return getProperInstance(Locale.getDefault()); 112 } 113 114 /** 115 * Returns the default complex format for the given locale. 116 * @param locale the specific locale used by the format. 117 * @return the complex format specific to the given locale. 118 */ 119 public static FractionFormat getProperInstance(final Locale locale) { 120 return new ProperFractionFormat(getDefaultNumberFormat(locale)); 121 } 122 123 /** 124 * Create a default number format. The default number format is based on 125 * {@link NumberFormat#getNumberInstance(java.util.Locale)} with the only 126 * customizing is the maximum number of fraction digits, which is set to 0. 127 * @return the default number format. 128 */ 129 protected static NumberFormat getDefaultNumberFormat() { 130 return getDefaultNumberFormat(Locale.getDefault()); 131 } 132 133 /** 134 * Formats a {@link Fraction} object to produce a string. The fraction is 135 * output in improper format. 136 * 137 * @param fraction the object to format. 138 * @param toAppendTo where the text is to be appended 139 * @param pos On input: an alignment field, if desired. On output: the 140 * offsets of the alignment field 141 * @return the value passed in as toAppendTo. 142 */ 143 public StringBuffer format(final Fraction fraction, 144 final StringBuffer toAppendTo, final FieldPosition pos) { 145 146 pos.setBeginIndex(0); 147 pos.setEndIndex(0); 148 149 getNumeratorFormat().format(fraction.getNumerator(), toAppendTo, pos); 150 toAppendTo.append(" / "); 151 getDenominatorFormat().format(fraction.getDenominator(), toAppendTo, 152 pos); 153 154 return toAppendTo; 155 } 156 157 /** 158 * Formats an object and appends the result to a StringBuffer. <code>obj</code> must be either a 159 * {@link Fraction} object or a {@link Number} object. Any other type of 160 * object will result in an {@link IllegalArgumentException} being thrown. 161 * 162 * @param obj the object to format. 163 * @param toAppendTo where the text is to be appended 164 * @param pos On input: an alignment field, if desired. On output: the 165 * offsets of the alignment field 166 * @return the value passed in as toAppendTo. 167 * @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition) 168 * @throws IllegalArgumentException is <code>obj</code> is not a valid type. 169 */ 170 @Override 171 public StringBuffer format(final Object obj, 172 final StringBuffer toAppendTo, final FieldPosition pos) { 173 StringBuffer ret = null; 174 175 if (obj instanceof Fraction) { 176 ret = format((Fraction) obj, toAppendTo, pos); 177 } else if (obj instanceof Number) { 178 try { 179 ret = format(new Fraction(((Number) obj).doubleValue()), 180 toAppendTo, pos); 181 } catch (ConvergenceException ex) { 182 throw MathRuntimeException.createIllegalArgumentException( 183 "cannot convert given object to a fraction number: {0}", 184 ex.getLocalizedMessage()); 185 } 186 } else { 187 throw MathRuntimeException.createIllegalArgumentException( 188 "cannot format given object as a fraction number"); 189 } 190 191 return ret; 192 } 193 194 /** 195 * Parses a string to produce a {@link Fraction} object. 196 * @param source the string to parse 197 * @return the parsed {@link Fraction} object. 198 * @exception ParseException if the beginning of the specified string 199 * cannot be parsed. 200 */ 201 @Override 202 public Fraction parse(final String source) throws ParseException { 203 final ParsePosition parsePosition = new ParsePosition(0); 204 final Fraction result = parse(source, parsePosition); 205 if (parsePosition.getIndex() == 0) { 206 throw MathRuntimeException.createParseException( 207 parsePosition.getErrorIndex(), 208 "unparseable fraction number: \"{0}\"", source); 209 } 210 return result; 211 } 212 213 /** 214 * Parses a string to produce a {@link Fraction} object. This method 215 * expects the string to be formatted as an improper fraction. 216 * @param source the string to parse 217 * @param pos input/ouput parsing parameter. 218 * @return the parsed {@link Fraction} object. 219 */ 220 @Override 221 public Fraction parse(final String source, final ParsePosition pos) { 222 final int initialIndex = pos.getIndex(); 223 224 // parse whitespace 225 parseAndIgnoreWhitespace(source, pos); 226 227 // parse numerator 228 final Number num = getNumeratorFormat().parse(source, pos); 229 if (num == null) { 230 // invalid integer number 231 // set index back to initial, error index should already be set 232 // character examined. 233 pos.setIndex(initialIndex); 234 return null; 235 } 236 237 // parse '/' 238 final int startIndex = pos.getIndex(); 239 final char c = parseNextCharacter(source, pos); 240 switch (c) { 241 case 0 : 242 // no '/' 243 // return num as a fraction 244 return new Fraction(num.intValue(), 1); 245 case '/' : 246 // found '/', continue parsing denominator 247 break; 248 default : 249 // invalid '/' 250 // set index back to initial, error index should be the last 251 // character examined. 252 pos.setIndex(initialIndex); 253 pos.setErrorIndex(startIndex); 254 return null; 255 } 256 257 // parse whitespace 258 parseAndIgnoreWhitespace(source, pos); 259 260 // parse denominator 261 final Number den = getDenominatorFormat().parse(source, pos); 262 if (den == null) { 263 // invalid integer number 264 // set index back to initial, error index should already be set 265 // character examined. 266 pos.setIndex(initialIndex); 267 return null; 268 } 269 270 return new Fraction(num.intValue(), den.intValue()); 271 } 272 273 }