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