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 package org.apache.commons.math.fraction;
18
19 import java.text.FieldPosition;
20 import java.text.NumberFormat;
21 import java.text.ParsePosition;
22
23 import org.apache.commons.math.util.MathUtils;
24
25 /**
26 * Formats a Fraction number in proper format. The number format for each of
27 * the whole number, numerator and, denominator can be configured.
28 * <p>
29 * Minus signs are only allowed in the whole number part - i.e.,
30 * "-3 1/2" is legitimate and denotes -7/2, but "-3 -1/2" is invalid and
31 * will result in a <code>ParseException</code>.</p>
32 *
33 * @since 1.1
34 * @version $Revision: 617953 $ $Date: 2008-02-02 22:54:00 -0700 (Sat, 02 Feb 2008) $
35 */
36 public class ProperFractionFormat extends FractionFormat {
37
38 /** Serializable version identifier */
39 private static final long serialVersionUID = -6337346779577272307L;
40
41 /** The format used for the whole number. */
42 private NumberFormat wholeFormat;
43
44 /**
45 * Create a proper formatting instance with the default number format for
46 * the whole, numerator, and denominator.
47 */
48 public ProperFractionFormat() {
49 this(getDefaultNumberFormat());
50 }
51
52 /**
53 * Create a proper formatting instance with a custom number format for the
54 * whole, numerator, and denominator.
55 * @param format the custom format for the whole, numerator, and
56 * denominator.
57 */
58 public ProperFractionFormat(NumberFormat format) {
59 this(format, (NumberFormat)format.clone(), (NumberFormat)format.clone());
60 }
61
62 /**
63 * Create a proper formatting instance with a custom number format for each
64 * of the whole, numerator, and denominator.
65 * @param wholeFormat the custom format for the whole.
66 * @param numeratorFormat the custom format for the numerator.
67 * @param denominatorFormat the custom format for the denominator.
68 */
69 public ProperFractionFormat(NumberFormat wholeFormat,
70 NumberFormat numeratorFormat,
71 NumberFormat denominatorFormat)
72 {
73 super(numeratorFormat, denominatorFormat);
74 setWholeFormat(wholeFormat);
75 }
76
77 /**
78 * Formats a {@link Fraction} object to produce a string. The fraction
79 * is output in proper format.
80 *
81 * @param fraction the object to format.
82 * @param toAppendTo where the text is to be appended
83 * @param pos On input: an alignment field, if desired. On output: the
84 * offsets of the alignment field
85 * @return the value passed in as toAppendTo.
86 */
87 public StringBuffer format(Fraction fraction, StringBuffer toAppendTo,
88 FieldPosition pos) {
89
90 pos.setBeginIndex(0);
91 pos.setEndIndex(0);
92
93 int num = fraction.getNumerator();
94 int den = fraction.getDenominator();
95 int whole = num / den;
96 num = num % den;
97
98 if (whole != 0) {
99 getWholeFormat().format(whole, toAppendTo, pos);
100 toAppendTo.append(' ');
101 num = Math.abs(num);
102 }
103 getNumeratorFormat().format(num, toAppendTo, pos);
104 toAppendTo.append(" / ");
105 getDenominatorFormat().format(den, toAppendTo,
106 pos);
107
108 return toAppendTo;
109 }
110
111 /**
112 * Access the whole format.
113 * @return the whole format.
114 */
115 public NumberFormat getWholeFormat() {
116 return wholeFormat;
117 }
118
119 /**
120 * Parses a string to produce a {@link Fraction} object. This method
121 * expects the string to be formatted as a proper fraction.
122 * <p>
123 * Minus signs are only allowed in the whole number part - i.e.,
124 * "-3 1/2" is legitimate and denotes -7/2, but "-3 -1/2" is invalid and
125 * will result in a <code>ParseException</code>.</p>
126 *
127 * @param source the string to parse
128 * @param pos input/ouput parsing parameter.
129 * @return the parsed {@link Fraction} object.
130 */
131 public Fraction parse(String source, ParsePosition pos) {
132 // try to parse improper fraction
133 Fraction ret = super.parse(source, pos);
134 if (ret != null) {
135 return ret;
136 }
137
138 int initialIndex = pos.getIndex();
139
140 // parse whitespace
141 parseAndIgnoreWhitespace(source, pos);
142
143 // parse whole
144 Number whole = getWholeFormat().parse(source, pos);
145 if (whole == null) {
146 // invalid integer number
147 // set index back to initial, error index should already be set
148 // character examined.
149 pos.setIndex(initialIndex);
150 return null;
151 }
152
153 // parse whitespace
154 parseAndIgnoreWhitespace(source, pos);
155
156 // parse numerator
157 Number num = getNumeratorFormat().parse(source, pos);
158 if (num == null) {
159 // invalid integer number
160 // set index back to initial, error index should already be set
161 // character examined.
162 pos.setIndex(initialIndex);
163 return null;
164 }
165
166 if (num.intValue() < 0) {
167 // minus signs should be leading, invalid expression
168 pos.setIndex(initialIndex);
169 return null;
170 }
171
172 // parse '/'
173 int startIndex = pos.getIndex();
174 char c = parseNextCharacter(source, pos);
175 switch (c) {
176 case 0 :
177 // no '/'
178 // return num as a fraction
179 return new Fraction(num.intValue(), 1);
180 case '/' :
181 // found '/', continue parsing denominator
182 break;
183 default :
184 // invalid '/'
185 // set index back to initial, error index should be the last
186 // character examined.
187 pos.setIndex(initialIndex);
188 pos.setErrorIndex(startIndex);
189 return null;
190 }
191
192 // parse whitespace
193 parseAndIgnoreWhitespace(source, pos);
194
195 // parse denominator
196 Number den = getDenominatorFormat().parse(source, pos);
197 if (den == null) {
198 // invalid integer number
199 // set index back to initial, error index should already be set
200 // character examined.
201 pos.setIndex(initialIndex);
202 return null;
203 }
204
205 if (den.intValue() < 0) {
206 // minus signs must be leading, invalid
207 pos.setIndex(initialIndex);
208 return null;
209 }
210
211 int w = whole.intValue();
212 int n = num.intValue();
213 int d = den.intValue();
214 return new Fraction(((Math.abs(w) * d) + n) * MathUtils.sign(w), d);
215 }
216
217 /**
218 * Modify the whole format.
219 * @param format The new whole format value.
220 * @throws IllegalArgumentException if <code>format</code> is
221 * <code>null</code>.
222 */
223 public void setWholeFormat(NumberFormat format) {
224 if (format == null) {
225 throw new IllegalArgumentException(
226 "whole format can not be null.");
227 }
228 this.wholeFormat = format;
229 }
230 }