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