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    *      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.math3.util;
18  
19  import java.text.FieldPosition;
20  import java.text.NumberFormat;
21  import java.text.ParsePosition;
22  import java.util.Locale;
23  
24  /**
25   * Base class for formatters of composite objects (complex numbers, vectors ...).
26   *
27   */
28  public class CompositeFormat {
29  
30      /**
31       * Class contains only static methods.
32       */
33      private CompositeFormat() {}
34  
35      /**
36       * Create a default number format.  The default number format is based on
37       * {@link NumberFormat#getInstance()} with the only customizing that the
38       * maximum number of fraction digits is set to 10.
39       * @return the default number format.
40       */
41      public static NumberFormat getDefaultNumberFormat() {
42          return getDefaultNumberFormat(Locale.getDefault());
43      }
44  
45      /**
46       * Create a default number format.  The default number format is based on
47       * {@link NumberFormat#getInstance(java.util.Locale)} with the only
48       * customizing that the maximum number of fraction digits is set to 10.
49       * @param locale the specific locale used by the format.
50       * @return the default number format specific to the given locale.
51       */
52      public static NumberFormat getDefaultNumberFormat(final Locale locale) {
53          final NumberFormat nf = NumberFormat.getInstance(locale);
54          nf.setMaximumFractionDigits(10);
55          return nf;
56      }
57  
58      /**
59       * Parses <code>source</code> until a non-whitespace character is found.
60       *
61       * @param source the string to parse
62       * @param pos input/output parsing parameter.  On output, <code>pos</code>
63       *        holds the index of the next non-whitespace character.
64       */
65      public static void parseAndIgnoreWhitespace(final String source,
66                                                  final ParsePosition pos) {
67          parseNextCharacter(source, pos);
68          pos.setIndex(pos.getIndex() - 1);
69      }
70  
71      /**
72       * Parses <code>source</code> until a non-whitespace character is found.
73       *
74       * @param source the string to parse
75       * @param pos input/output parsing parameter.
76       * @return the first non-whitespace character.
77       */
78      public static char parseNextCharacter(final String source,
79                                            final ParsePosition pos) {
80           int index = pos.getIndex();
81           final int n = source.length();
82           char ret = 0;
83  
84           if (index < n) {
85               char c;
86               do {
87                   c = source.charAt(index++);
88               } while (Character.isWhitespace(c) && index < n);
89               pos.setIndex(index);
90  
91               if (index < n) {
92                   ret = c;
93               }
94           }
95  
96           return ret;
97      }
98  
99      /**
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 }