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.math.BigInteger;
25  import java.text.FieldPosition;
26  import java.text.NumberFormat;
27  import java.text.ParsePosition;
28  
29  import org.hipparchus.exception.LocalizedCoreFormats;
30  import org.hipparchus.util.MathUtils;
31  
32  /**
33   * Formats a BigFraction number in proper format. The number format
34   * for each of 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 ProperBigFractionFormat extends BigFractionFormat {
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 ProperBigFractionFormat() {
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 ProperBigFractionFormat(final 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 ProperBigFractionFormat(final NumberFormat wholeFormat,
75                                     final NumberFormat numeratorFormat,
76                                     final 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 BigFraction} object to produce a string.  The BigFraction
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(final BigFraction fraction,
95                                 final StringBuffer toAppendTo,
96                                 final FieldPosition pos) {
97  
98          pos.setBeginIndex(0);
99          pos.setEndIndex(0);
100 
101         BigInteger num = fraction.getNumerator();
102         BigInteger den = fraction.getDenominator();
103         BigInteger whole = num.divide(den);
104         num = num.remainder(den);
105 
106         if (!BigInteger.ZERO.equals(whole)) {
107             getWholeFormat().format(whole, toAppendTo, pos);
108             toAppendTo.append(' ');
109             if (num.compareTo(BigInteger.ZERO) < 0) {
110                 num = num.negate();
111             }
112         }
113         getNumeratorFormat().format(num, toAppendTo, pos);
114         toAppendTo.append(" / ");
115         getDenominatorFormat().format(den, toAppendTo, pos);
116 
117         return toAppendTo;
118     }
119 
120     /**
121      * Access the whole format.
122      * @return the whole format.
123      */
124     public NumberFormat getWholeFormat() {
125         return wholeFormat;
126     }
127 
128     /**
129      * Parses a string to produce a {@link BigFraction} object.  This method
130      * expects the string to be formatted as a proper BigFraction.
131      * <p>
132      * Minus signs are only allowed in the whole number part - i.e.,
133      * "-3 1/2" is legitimate and denotes -7/2, but "-3 -1/2" is invalid and
134      * will result in a <code>ParseException</code>.</p>
135      *
136      * @param source the string to parse
137      * @param pos input/ouput parsing parameter.
138      * @return the parsed {@link BigFraction} object.
139      */
140     @Override
141     public BigFraction parse(final String source, final ParsePosition pos) {
142         // try to parse improper BigFraction
143         BigFraction ret = super.parse(source, pos);
144         if (ret != null) {
145             return ret;
146         }
147 
148         final int initialIndex = pos.getIndex();
149 
150         // parse whitespace
151         parseAndIgnoreWhitespace(source, pos);
152 
153         // parse whole
154         BigInteger whole = parseNextBigInteger(source, pos);
155         if (whole == null) {
156             // invalid integer number
157             // set index back to initial, error index should already be set
158             // character examined.
159             pos.setIndex(initialIndex);
160             return null;
161         }
162 
163         // parse whitespace
164         parseAndIgnoreWhitespace(source, pos);
165 
166         // parse numerator
167         BigInteger num = parseNextBigInteger(source, pos);
168         if (num == null) {
169             // invalid integer number
170             // set index back to initial, error index should already be set
171             // character examined.
172             pos.setIndex(initialIndex);
173             return null;
174         }
175 
176         if (num.compareTo(BigInteger.ZERO) < 0) {
177             // minus signs should be leading, invalid expression
178             pos.setIndex(initialIndex);
179             return null;
180         }
181 
182         // parse '/'
183         final int startIndex = pos.getIndex();
184         final char c = parseNextCharacter(source, pos);
185         switch (c) {
186         case 0 :
187             // no '/'
188             // return num as a BigFraction
189             return new BigFraction(num);
190         case '/' :
191             // found '/', continue parsing denominator
192             break;
193         default :
194             // invalid '/'
195             // set index back to initial, error index should be the last
196             // character examined.
197             pos.setIndex(initialIndex);
198             pos.setErrorIndex(startIndex);
199             return null;
200         }
201 
202         // parse whitespace
203         parseAndIgnoreWhitespace(source, pos);
204 
205         // parse denominator
206         final BigInteger den = parseNextBigInteger(source, pos);
207         if (den == null) {
208             // invalid integer number
209             // set index back to initial, error index should already be set
210             // character examined.
211             pos.setIndex(initialIndex);
212             return null;
213         }
214 
215         if (den.compareTo(BigInteger.ZERO) < 0) {
216             // minus signs must be leading, invalid
217             pos.setIndex(initialIndex);
218             return null;
219         }
220 
221         boolean wholeIsNeg = whole.compareTo(BigInteger.ZERO) < 0;
222         if (wholeIsNeg) {
223             whole = whole.negate();
224         }
225         num = whole.multiply(den).add(num);
226         if (wholeIsNeg) {
227             num = num.negate();
228         }
229 
230         return new BigFraction(num, den);
231 
232     }
233 }