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 */
017package org.apache.commons.math4.util;
018
019import java.text.FieldPosition;
020import java.text.NumberFormat;
021import java.text.ParsePosition;
022import java.util.Locale;
023
024/**
025 * Base class for formatters of composite objects (complex numbers, vectors ...).
026 *
027 */
028public class CompositeFormat {
029
030    /**
031     * Class contains only static methods.
032     */
033    private CompositeFormat() {}
034
035    /**
036     * Create a default number format.  The default number format is based on
037     * {@link NumberFormat#getInstance()} with the only customizing that the
038     * maximum number of fraction digits is set to 10.
039     * @return the default number format.
040     */
041    public static NumberFormat getDefaultNumberFormat() {
042        return getDefaultNumberFormat(Locale.getDefault());
043    }
044
045    /**
046     * Create a default number format.  The default number format is based on
047     * {@link NumberFormat#getInstance(java.util.Locale)} with the only
048     * customizing that the maximum number of fraction digits is set to 10.
049     * @param locale the specific locale used by the format.
050     * @return the default number format specific to the given locale.
051     */
052    public static NumberFormat getDefaultNumberFormat(final Locale locale) {
053        final NumberFormat nf = NumberFormat.getInstance(locale);
054        nf.setMaximumFractionDigits(10);
055        return nf;
056    }
057
058    /**
059     * Parses <code>source</code> until a non-whitespace character is found.
060     *
061     * @param source the string to parse
062     * @param pos input/output parsing parameter.  On output, <code>pos</code>
063     *        holds the index of the next non-whitespace character.
064     */
065    public static void parseAndIgnoreWhitespace(final String source,
066                                                final ParsePosition pos) {
067        parseNextCharacter(source, pos);
068        pos.setIndex(pos.getIndex() - 1);
069    }
070
071    /**
072     * Parses <code>source</code> until a non-whitespace character is found.
073     *
074     * @param source the string to parse
075     * @param pos input/output parsing parameter.
076     * @return the first non-whitespace character.
077     */
078    public static char parseNextCharacter(final String source,
079                                          final ParsePosition pos) {
080         int index = pos.getIndex();
081         final int n = source.length();
082         char ret = 0;
083
084         if (index < n) {
085             char c;
086             do {
087                 c = source.charAt(index++);
088             } while (Character.isWhitespace(c) && index < n);
089             pos.setIndex(index);
090
091             if (index < n) {
092                 ret = c;
093             }
094         }
095
096         return ret;
097    }
098
099    /**
100     * Parses <code>source</code> for special double values.  These values
101     * include Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY.
102     *
103     * @param source the string to parse
104     * @param value the special value to parse.
105     * @param pos input/output parsing parameter.
106     * @return the special number.
107     */
108    private static Number parseNumber(final String source, final double value,
109                                      final ParsePosition pos) {
110        Number ret = null;
111
112        StringBuilder sb = new StringBuilder();
113        sb.append('(');
114        sb.append(value);
115        sb.append(')');
116
117        final int n = sb.length();
118        final int startIndex = pos.getIndex();
119        final int endIndex = startIndex + n;
120        if (endIndex < source.length() &&
121            source.substring(startIndex, endIndex).compareTo(sb.toString()) == 0) {
122            ret = Double.valueOf(value);
123            pos.setIndex(endIndex);
124        }
125
126        return ret;
127    }
128
129    /**
130     * Parses <code>source</code> for a number.  This method can parse normal,
131     * numeric values as well as special values.  These special values include
132     * Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY.
133     *
134     * @param source the string to parse
135     * @param format the number format used to parse normal, numeric values.
136     * @param pos input/output parsing parameter.
137     * @return the parsed number.
138     */
139    public static Number parseNumber(final String source, final NumberFormat format,
140                                     final ParsePosition pos) {
141        final int startIndex = pos.getIndex();
142        Number number = format.parse(source, pos);
143        final int endIndex = pos.getIndex();
144
145        // check for error parsing number
146        if (startIndex == endIndex) {
147            // try parsing special numbers
148            final double[] special = {
149                Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY
150            };
151            for (int i = 0; i < special.length; ++i) {
152                number = parseNumber(source, special[i], pos);
153                if (number != null) {
154                    break;
155                }
156            }
157        }
158
159        return number;
160    }
161
162    /**
163     * Parse <code>source</code> for an expected fixed string.
164     * @param source the string to parse
165     * @param expected expected string
166     * @param pos input/output parsing parameter.
167     * @return true if the expected string was there
168     */
169    public static boolean parseFixedstring(final String source,
170                                           final String expected,
171                                           final ParsePosition pos) {
172
173        final int startIndex = pos.getIndex();
174        final int endIndex = startIndex + expected.length();
175        if ((startIndex >= source.length()) ||
176            (endIndex > source.length()) ||
177            (source.substring(startIndex, endIndex).compareTo(expected) != 0)) {
178            // set index back to start, error index should be the start index
179            pos.setIndex(startIndex);
180            pos.setErrorIndex(startIndex);
181            return false;
182        }
183
184        // the string was here
185        pos.setIndex(endIndex);
186        return true;
187    }
188
189    /**
190     * Formats a double value to produce a string.  In general, the value is
191     * formatted using the formatting rules of <code>format</code>.  There are
192     * three exceptions to this:
193     * <ol>
194     * <li>NaN is formatted as '(NaN)'</li>
195     * <li>Positive infinity is formatted as '(Infinity)'</li>
196     * <li>Negative infinity is formatted as '(-Infinity)'</li>
197     * </ol>
198     *
199     * @param value the double to format.
200     * @param format the format used.
201     * @param toAppendTo where the text is to be appended
202     * @param pos On input: an alignment field, if desired. On output: the
203     *            offsets of the alignment field
204     * @return the value passed in as toAppendTo.
205     */
206    public static StringBuffer formatDouble(final double value, final NumberFormat format,
207                                            final StringBuffer toAppendTo,
208                                            final FieldPosition pos) {
209        if( Double.isNaN(value) || Double.isInfinite(value) ) {
210            toAppendTo.append('(');
211            toAppendTo.append(value);
212            toAppendTo.append(')');
213        } else {
214            format.format(value, toAppendTo, pos);
215        }
216        return toAppendTo;
217    }
218}