001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.math3.fraction;
019
020import java.io.Serializable;
021import java.text.FieldPosition;
022import java.text.NumberFormat;
023import java.text.ParsePosition;
024import java.util.Locale;
025
026import org.apache.commons.math3.exception.NullArgumentException;
027import org.apache.commons.math3.exception.util.LocalizedFormats;
028
029/**
030 * Common part shared by both {@link FractionFormat} and {@link BigFractionFormat}.
031 * @since 2.0
032 */
033public abstract class AbstractFormat extends NumberFormat implements Serializable {
034
035    /** Serializable version identifier. */
036    private static final long serialVersionUID = -6981118387974191891L;
037
038    /** The format used for the denominator. */
039    private NumberFormat denominatorFormat;
040
041    /** The format used for the numerator. */
042    private NumberFormat numeratorFormat;
043
044    /**
045     * Create an improper formatting instance with the default number format
046     * for the numerator and denominator.
047     */
048    protected AbstractFormat() {
049        this(getDefaultNumberFormat());
050    }
051
052    /**
053     * Create an improper formatting instance with a custom number format for
054     * both the numerator and denominator.
055     * @param format the custom format for both the numerator and denominator.
056     */
057    protected AbstractFormat(final NumberFormat format) {
058        this(format, (NumberFormat) format.clone());
059    }
060
061    /**
062     * Create an improper formatting instance with a custom number format for
063     * the numerator and a custom number format for the denominator.
064     * @param numeratorFormat the custom format for the numerator.
065     * @param denominatorFormat the custom format for the denominator.
066     */
067    protected AbstractFormat(final NumberFormat numeratorFormat,
068                             final NumberFormat denominatorFormat) {
069        this.numeratorFormat   = numeratorFormat;
070        this.denominatorFormat = denominatorFormat;
071    }
072
073    /**
074     * Create a default number format.  The default number format is based on
075     * {@link NumberFormat#getNumberInstance(java.util.Locale)}. The only
076     * customization is the maximum number of BigFraction digits, which is set to 0.
077     * @return the default number format.
078     */
079    protected static NumberFormat getDefaultNumberFormat() {
080        return getDefaultNumberFormat(Locale.getDefault());
081    }
082
083    /**
084     * Create a default number format.  The default number format is based on
085     * {@link NumberFormat#getNumberInstance(java.util.Locale)}. The only
086     * customization is the maximum number of BigFraction digits, which is set to 0.
087     * @param locale the specific locale used by the format.
088     * @return the default number format specific to the given locale.
089     */
090    protected static NumberFormat getDefaultNumberFormat(final Locale locale) {
091        final NumberFormat nf = NumberFormat.getNumberInstance(locale);
092        nf.setMaximumFractionDigits(0);
093        nf.setParseIntegerOnly(true);
094        return nf;
095    }
096
097    /**
098     * Access the denominator format.
099     * @return the denominator format.
100     */
101    public NumberFormat getDenominatorFormat() {
102        return denominatorFormat;
103    }
104
105    /**
106     * Access the numerator format.
107     * @return the numerator format.
108     */
109    public NumberFormat getNumeratorFormat() {
110        return numeratorFormat;
111    }
112
113    /**
114     * Modify the denominator format.
115     * @param format the new denominator format value.
116     * @throws NullArgumentException if {@code format} is {@code null}.
117     */
118    public void setDenominatorFormat(final NumberFormat format) {
119        if (format == null) {
120            throw new NullArgumentException(LocalizedFormats.DENOMINATOR_FORMAT);
121        }
122        this.denominatorFormat = format;
123    }
124
125    /**
126     * Modify the numerator format.
127     * @param format the new numerator format value.
128     * @throws NullArgumentException if {@code format} is {@code null}.
129     */
130    public void setNumeratorFormat(final NumberFormat format) {
131        if (format == null) {
132            throw new NullArgumentException(LocalizedFormats.NUMERATOR_FORMAT);
133        }
134        this.numeratorFormat = format;
135    }
136
137    /**
138     * Parses <code>source</code> until a non-whitespace character is found.
139     * @param source the string to parse
140     * @param pos input/output parsing parameter.  On output, <code>pos</code>
141     *        holds the index of the next non-whitespace character.
142     */
143    protected static void parseAndIgnoreWhitespace(final String source,
144                                                   final ParsePosition pos) {
145        parseNextCharacter(source, pos);
146        pos.setIndex(pos.getIndex() - 1);
147    }
148
149    /**
150     * Parses <code>source</code> until a non-whitespace character is found.
151     * @param source the string to parse
152     * @param pos input/output parsing parameter.
153     * @return the first non-whitespace character.
154     */
155    protected static char parseNextCharacter(final String source,
156                                             final ParsePosition pos) {
157         int index = pos.getIndex();
158         final int n = source.length();
159         char ret = 0;
160
161         if (index < n) {
162             char c;
163             do {
164                 c = source.charAt(index++);
165             } while (Character.isWhitespace(c) && index < n);
166             pos.setIndex(index);
167
168             if (index < n) {
169                 ret = c;
170             }
171         }
172
173         return ret;
174    }
175
176    /**
177     * Formats a double value as a fraction and appends the result to a StringBuffer.
178     *
179     * @param value the double value to format
180     * @param buffer StringBuffer to append to
181     * @param position On input: an alignment field, if desired. On output: the
182     *            offsets of the alignment field
183     * @return a reference to the appended buffer
184     * @see #format(Object, StringBuffer, FieldPosition)
185     */
186    @Override
187    public StringBuffer format(final double value,
188                               final StringBuffer buffer, final FieldPosition position) {
189        return format(Double.valueOf(value), buffer, position);
190    }
191
192
193    /**
194     * Formats a long value as a fraction and appends the result to a StringBuffer.
195     *
196     * @param value the long value to format
197     * @param buffer StringBuffer to append to
198     * @param position On input: an alignment field, if desired. On output: the
199     *            offsets of the alignment field
200     * @return a reference to the appended buffer
201     * @see #format(Object, StringBuffer, FieldPosition)
202     */
203    @Override
204    public StringBuffer format(final long value,
205                               final StringBuffer buffer, final FieldPosition position) {
206        return format(Long.valueOf(value), buffer, position);
207    }
208
209}