001: /*
002: * Copyright 2005 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.apache.commons.math.fraction;
018:
019: import java.io.Serializable;
020: import java.text.FieldPosition;
021: import java.text.Format;
022: import java.text.NumberFormat;
023: import java.text.ParseException;
024: import java.text.ParsePosition;
025: import java.util.Locale;
026:
027: import org.apache.commons.math.ConvergenceException;
028:
029: /**
030: * Formats a Fraction number in proper format or improper format. The number
031: * format for each of the whole number, numerator and, denominator can be
032: * configured.
033: *
034: * @since 1.1
035: * @version $Revision: 348519 $ $Date: 2005-11-23 12:12:18 -0700 (Wed, 23 Nov 2005) $
036: */
037: public class FractionFormat extends Format implements Serializable {
038:
039: /** Serializable version identifier */
040: private static final long serialVersionUID = -6337346779577272306L;
041:
042: /** The format used for the denominator. */
043: private NumberFormat denominatorFormat;
044:
045: /** The format used for the numerator. */
046: private NumberFormat numeratorFormat;
047:
048: /**
049: * Create an improper formatting instance with the default number format
050: * for the numerator and denominator.
051: */
052: public FractionFormat() {
053: this (getDefaultNumberFormat());
054: }
055:
056: /**
057: * Create an improper formatting instance with a custom number format for
058: * both the numerator and denominator.
059: * @param format the custom format for both the numerator and denominator.
060: */
061: public FractionFormat(NumberFormat format) {
062: this (format, (NumberFormat) format.clone());
063: }
064:
065: /**
066: * Create an improper formatting instance with a custom number format for
067: * the numerator and a custom number format for the denominator.
068: * @param numeratorFormat the custom format for the numerator.
069: * @param denominatorFormat the custom format for the denominator.
070: */
071: public FractionFormat(NumberFormat numeratorFormat,
072: NumberFormat denominatorFormat) {
073: super ();
074: this .numeratorFormat = numeratorFormat;
075: this .denominatorFormat = denominatorFormat;
076: }
077:
078: /**
079: * This static method calls formatFraction() on a default instance of
080: * FractionFormat.
081: *
082: * @param f Fraction object to format
083: * @return A formatted fraction in proper form.
084: */
085: public static String formatFraction(Fraction f) {
086: return getImproperInstance().format(f);
087: }
088:
089: /**
090: * Get the set of locales for which complex formats are available. This
091: * is the same set as the {@link NumberFormat} set.
092: * @return available complex format locales.
093: */
094: public static Locale[] getAvailableLocales() {
095: return NumberFormat.getAvailableLocales();
096: }
097:
098: /**
099: * Returns the default complex format for the current locale.
100: * @return the default complex format.
101: */
102: public static FractionFormat getImproperInstance() {
103: return getImproperInstance(Locale.getDefault());
104: }
105:
106: /**
107: * Returns the default complex format for the given locale.
108: * @param locale the specific locale used by the format.
109: * @return the complex format specific to the given locale.
110: */
111: public static FractionFormat getImproperInstance(Locale locale) {
112: NumberFormat f = getDefaultNumberFormat(locale);
113: return new FractionFormat(f);
114: }
115:
116: /**
117: * Returns the default complex format for the current locale.
118: * @return the default complex format.
119: */
120: public static FractionFormat getProperInstance() {
121: return getProperInstance(Locale.getDefault());
122: }
123:
124: /**
125: * Returns the default complex format for the given locale.
126: * @param locale the specific locale used by the format.
127: * @return the complex format specific to the given locale.
128: */
129: public static FractionFormat getProperInstance(Locale locale) {
130: NumberFormat f = getDefaultNumberFormat(locale);
131: return new ProperFractionFormat(f);
132: }
133:
134: /**
135: * Create a default number format. The default number format is based on
136: * {@link NumberFormat#getNumberInstance(java.util.Locale)} with the only
137: * customizing is the maximum number of fraction digits, which is set to 0.
138: * @return the default number format.
139: */
140: protected static NumberFormat getDefaultNumberFormat() {
141: return getDefaultNumberFormat(Locale.getDefault());
142: }
143:
144: /**
145: * Create a default number format. The default number format is based on
146: * {@link NumberFormat#getNumberInstance(java.util.Locale)} with the only
147: * customizing is the maximum number of fraction digits, which is set to 0.
148: * @param locale the specific locale used by the format.
149: * @return the default number format specific to the given locale.
150: */
151: private static NumberFormat getDefaultNumberFormat(Locale locale) {
152: NumberFormat nf = NumberFormat.getNumberInstance(locale);
153: nf.setMaximumFractionDigits(0);
154: nf.setParseIntegerOnly(true);
155: return nf;
156: }
157:
158: /**
159: * Formats a {@link Fraction} object to produce a string. The fraction is
160: * output in improper format.
161: *
162: * @param fraction 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: */
168: public StringBuffer format(Fraction fraction,
169: StringBuffer toAppendTo, FieldPosition pos) {
170:
171: pos.setBeginIndex(0);
172: pos.setEndIndex(0);
173:
174: getNumeratorFormat().format(fraction.getNumerator(),
175: toAppendTo, pos);
176: toAppendTo.append(" / ");
177: getDenominatorFormat().format(fraction.getDenominator(),
178: toAppendTo, 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: StringBuffer ret = null;
199:
200: if (obj instanceof Fraction) {
201: ret = format((Fraction) obj, toAppendTo, pos);
202: } else if (obj instanceof Number) {
203: try {
204: ret = format(
205: 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(String source,
357: ParsePosition pos) {
358: parseNextCharacter(source, pos);
359: pos.setIndex(pos.getIndex() - 1);
360: }
361:
362: /**
363: * Parses <code>source</code> until a non-whitespace character is found.
364: * @param source the string to parse
365: * @param pos input/ouput parsing parameter.
366: * @return the first non-whitespace character.
367: */
368: protected static char parseNextCharacter(String source,
369: 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: }
|