1 /* 2 * Copyright 2005 The Apache Software Foundation. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.apache.commons.math.fraction; 18 19 import java.io.Serializable; 20 import java.text.FieldPosition; 21 import java.text.Format; 22 import java.text.NumberFormat; 23 import java.text.ParseException; 24 import java.text.ParsePosition; 25 import java.util.Locale; 26 27 import org.apache.commons.math.ConvergenceException; 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: 348519 $ $Date: 2005-11-23 12:12:18 -0700 (Wed, 23 Nov 2005) $ 36 */ 37 public class FractionFormat extends Format implements Serializable { 38 39 /** Serializable version identifier */ 40 private static final long serialVersionUID = -6337346779577272306L; 41 42 /** The format used for the denominator. */ 43 private NumberFormat denominatorFormat; 44 45 /** The format used for the numerator. */ 46 private NumberFormat numeratorFormat; 47 48 /** 49 * Create an improper formatting instance with the default number format 50 * for the numerator and denominator. 51 */ 52 public FractionFormat() { 53 this(getDefaultNumberFormat()); 54 } 55 56 /** 57 * Create an improper formatting instance with a custom number format for 58 * both the numerator and denominator. 59 * @param format the custom format for both the numerator and denominator. 60 */ 61 public FractionFormat(NumberFormat format) { 62 this(format, (NumberFormat)format.clone()); 63 } 64 65 /** 66 * Create an improper formatting instance with a custom number format for 67 * the numerator and a custom number format for the denominator. 68 * @param numeratorFormat the custom format for the numerator. 69 * @param denominatorFormat the custom format for the denominator. 70 */ 71 public FractionFormat(NumberFormat numeratorFormat, 72 NumberFormat denominatorFormat) 73 { 74 super(); 75 this.numeratorFormat = numeratorFormat; 76 this.denominatorFormat = denominatorFormat; 77 } 78 79 /** 80 * This static method calls formatFraction() on a default instance of 81 * FractionFormat. 82 * 83 * @param f Fraction object to format 84 * @return A formatted fraction in proper form. 85 */ 86 public static String formatFraction(Fraction f) { 87 return getImproperInstance().format(f); 88 } 89 90 /** 91 * Get the set of locales for which complex formats are available. This 92 * is the same set as the {@link NumberFormat} set. 93 * @return available complex format locales. 94 */ 95 public static Locale[] getAvailableLocales() { 96 return NumberFormat.getAvailableLocales(); 97 } 98 99 /** 100 * Returns the default complex format for the current locale. 101 * @return the default complex format. 102 */ 103 public static FractionFormat getImproperInstance() { 104 return getImproperInstance(Locale.getDefault()); 105 } 106 107 /** 108 * Returns the default complex format for the given locale. 109 * @param locale the specific locale used by the format. 110 * @return the complex format specific to the given locale. 111 */ 112 public static FractionFormat getImproperInstance(Locale locale) { 113 NumberFormat f = getDefaultNumberFormat(locale); 114 return new FractionFormat(f); 115 } 116 117 /** 118 * Returns the default complex format for the current locale. 119 * @return the default complex format. 120 */ 121 public static FractionFormat getProperInstance() { 122 return getProperInstance(Locale.getDefault()); 123 } 124 125 /** 126 * Returns the default complex format for the given locale. 127 * @param locale the specific locale used by the format. 128 * @return the complex format specific to the given locale. 129 */ 130 public static FractionFormat getProperInstance(Locale locale) { 131 NumberFormat f = getDefaultNumberFormat(locale); 132 return new ProperFractionFormat(f); 133 } 134 135 /** 136 * Create a default number format. The default number format is based on 137 * {@link NumberFormat#getNumberInstance(java.util.Locale)} with the only 138 * customizing is the maximum number of fraction digits, which is set to 0. 139 * @return the default number format. 140 */ 141 protected static NumberFormat getDefaultNumberFormat() { 142 return getDefaultNumberFormat(Locale.getDefault()); 143 } 144 145 /** 146 * Create a default number format. The default number format is based on 147 * {@link NumberFormat#getNumberInstance(java.util.Locale)} with the only 148 * customizing is the maximum number of fraction digits, which is set to 0. 149 * @param locale the specific locale used by the format. 150 * @return the default number format specific to the given locale. 151 */ 152 private static NumberFormat getDefaultNumberFormat(Locale locale) { 153 NumberFormat nf = NumberFormat.getNumberInstance(locale); 154 nf.setMaximumFractionDigits(0); 155 nf.setParseIntegerOnly(true); 156 return nf; 157 } 158 159 /** 160 * Formats a {@link Fraction} object to produce a string. The fraction is 161 * output in improper format. 162 * 163 * @param fraction the object to format. 164 * @param toAppendTo where the text is to be appended 165 * @param pos On input: an alignment field, if desired. On output: the 166 * offsets of the alignment field 167 * @return the value passed in as toAppendTo. 168 */ 169 public StringBuffer format(Fraction fraction, StringBuffer toAppendTo, 170 FieldPosition pos) { 171 172 pos.setBeginIndex(0); 173 pos.setEndIndex(0); 174 175 getNumeratorFormat().format(fraction.getNumerator(), toAppendTo, pos); 176 toAppendTo.append(" / "); 177 getDenominatorFormat().format(fraction.getDenominator(), toAppendTo, 178 pos); 179 180 return toAppendTo; 181 } 182 183 /** 184 * Formats a object to produce a string. <code>obj</code> must be either a 185 * {@link Fraction} object or a {@link Number} object. Any other type of 186 * object will result in an {@link IllegalArgumentException} being thrown. 187 * 188 * @param obj the object to format. 189 * @param toAppendTo where the text is to be appended 190 * @param pos On input: an alignment field, if desired. On output: the 191 * offsets of the alignment field 192 * @return the value passed in as toAppendTo. 193 * @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition) 194 * @throws IllegalArgumentException is <code>obj</code> is not a valid type. 195 */ 196 public StringBuffer format(Object obj, StringBuffer toAppendTo, 197 FieldPosition pos) 198 { 199 StringBuffer ret = null; 200 201 if (obj instanceof Fraction) { 202 ret = format( (Fraction)obj, toAppendTo, pos); 203 } else if (obj instanceof Number) { 204 try { 205 ret = format( new Fraction(((Number)obj).doubleValue()), 206 toAppendTo, pos); 207 } catch (ConvergenceException ex) { 208 throw new IllegalArgumentException( 209 "Cannot convert given object to a fraction."); 210 } 211 } else { 212 throw new IllegalArgumentException( 213 "Cannot format given object as a fraction"); 214 } 215 216 return ret; 217 } 218 219 /** 220 * Access the denominator format. 221 * @return the denominator format. 222 */ 223 public NumberFormat getDenominatorFormat() { 224 return denominatorFormat; 225 } 226 227 /** 228 * Access the numerator format. 229 * @return the numerator format. 230 */ 231 public NumberFormat getNumeratorFormat() { 232 return numeratorFormat; 233 } 234 235 /** 236 * Parses a string to produce a {@link Fraction} object. 237 * @param source the string to parse 238 * @return the parsed {@link Fraction} object. 239 * @exception ParseException if the beginning of the specified string 240 * cannot be parsed. 241 */ 242 public Fraction parse(String source) throws ParseException { 243 ParsePosition parsePosition = new ParsePosition(0); 244 Fraction result = parse(source, parsePosition); 245 if (parsePosition.getIndex() == 0) { 246 throw new ParseException("Unparseable fraction number: \"" + 247 source + "\"", parsePosition.getErrorIndex()); 248 } 249 return result; 250 } 251 252 /** 253 * Parses a string to produce a {@link Fraction} object. This method 254 * expects the string to be formatted as an improper fraction. 255 * @param source the string to parse 256 * @param pos input/ouput parsing parameter. 257 * @return the parsed {@link Fraction} object. 258 */ 259 public Fraction parse(String source, ParsePosition pos) { 260 int initialIndex = pos.getIndex(); 261 262 // parse whitespace 263 parseAndIgnoreWhitespace(source, pos); 264 265 // parse numerator 266 Number num = getNumeratorFormat().parse(source, pos); 267 if (num == null) { 268 // invalid integer number 269 // set index back to initial, error index should already be set 270 // character examined. 271 pos.setIndex(initialIndex); 272 return null; 273 } 274 275 // parse '/' 276 int startIndex = pos.getIndex(); 277 char c = parseNextCharacter(source, pos); 278 switch (c) { 279 case 0 : 280 // no '/' 281 // return num as a fraction 282 return new Fraction(num.intValue(), 1); 283 case '/' : 284 // found '/', continue parsing denominator 285 break; 286 default : 287 // invalid '/' 288 // set index back to initial, error index should be the last 289 // character examined. 290 pos.setIndex(initialIndex); 291 pos.setErrorIndex(startIndex); 292 return null; 293 } 294 295 // parse whitespace 296 parseAndIgnoreWhitespace(source, pos); 297 298 // parse denominator 299 Number den = getDenominatorFormat().parse(source, pos); 300 if (den == null) { 301 // invalid integer number 302 // set index back to initial, error index should already be set 303 // character examined. 304 pos.setIndex(initialIndex); 305 return null; 306 } 307 308 return new Fraction(num.intValue(), den.intValue()); 309 } 310 311 /** 312 * Parses a string to produce a object. 313 * @param source the string to parse 314 * @param pos input/ouput parsing parameter. 315 * @return the parsed object. 316 * @see java.text.Format#parseObject(java.lang.String, java.text.ParsePosition) 317 */ 318 public Object parseObject(String source, ParsePosition pos) { 319 return parse(source, pos); 320 } 321 322 /** 323 * Modify the denominator format. 324 * @param format the new denominator format value. 325 * @throws IllegalArgumentException if <code>format</code> is 326 * <code>null</code>. 327 */ 328 public void setDenominatorFormat(NumberFormat format) { 329 if (format == null) { 330 throw new IllegalArgumentException( 331 "denominator format can not be null."); 332 } 333 this.denominatorFormat = format; 334 } 335 336 /** 337 * Modify the numerator format. 338 * @param format the new numerator format value. 339 * @throws IllegalArgumentException if <code>format</code> is 340 * <code>null</code>. 341 */ 342 public void setNumeratorFormat(NumberFormat format) { 343 if (format == null) { 344 throw new IllegalArgumentException( 345 "numerator format can not be null."); 346 } 347 this.numeratorFormat = format; 348 } 349 350 /** 351 * Parses <code>source</code> until a non-whitespace character is found. 352 * @param source the string to parse 353 * @param pos input/ouput parsing parameter. On output, <code>pos</code> 354 * holds the index of the next non-whitespace character. 355 */ 356 protected static void parseAndIgnoreWhitespace( 357 String source, ParsePosition pos) 358 { 359 parseNextCharacter(source, pos); 360 pos.setIndex(pos.getIndex() - 1); 361 } 362 363 /** 364 * Parses <code>source</code> until a non-whitespace character is found. 365 * @param source the string to parse 366 * @param pos input/ouput parsing parameter. 367 * @return the first non-whitespace character. 368 */ 369 protected static char parseNextCharacter(String source, ParsePosition pos) { 370 int index = pos.getIndex(); 371 int n = source.length(); 372 char ret = 0; 373 374 if (index < n) { 375 char c; 376 do { 377 c = source.charAt(index++); 378 } while (Character.isWhitespace(c) && index < n); 379 pos.setIndex(index); 380 381 if (index < n) { 382 ret = c; 383 } 384 } 385 386 return ret; 387 } 388 }