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.math.util;
18
19 import java.text.FieldPosition;
20 import java.text.Format;
21 import java.text.NumberFormat;
22 import java.text.ParsePosition;
23 import java.util.Locale;
24
25 /**
26 * Base class for formatters of composite objects (complex numbers, vectors ...).
27 *
28 * @author Apache Software Foundation
29 * @version $Revision: 705231 $ $Date: 2008-10-16 08:49:13 -0400 (Thu, 16 Oct 2008) $
30 */
31 public abstract class CompositeFormat extends Format {
32
33 /** Serializable version identifier. */
34 private static final long serialVersionUID = 5358685519349262494L;
35
36 /**
37 * Create a default number format. The default number format is based on
38 * {@link NumberFormat#getInstance()} with the only customizing that the
39 * maximum number of fraction digits is set to 2.
40 * @return the default number format.
41 */
42 protected static NumberFormat getDefaultNumberFormat() {
43 return getDefaultNumberFormat(Locale.getDefault());
44 }
45
46 /**
47 * Create a default number format. The default number format is based on
48 * {@link NumberFormat#getInstance(java.util.Locale)} with the only
49 * customizing that the maximum number of fraction digits is set to 2.
50 * @param locale the specific locale used by the format.
51 * @return the default number format specific to the given locale.
52 */
53 protected static NumberFormat getDefaultNumberFormat(final Locale locale) {
54 final NumberFormat nf = NumberFormat.getInstance(locale);
55 nf.setMaximumFractionDigits(2);
56 return nf;
57 }
58
59 /**
60 * Parses <code>source</code> until a non-whitespace character is found.
61 *
62 * @param source the string to parse
63 * @param pos input/ouput parsing parameter. On output, <code>pos</code>
64 * holds the index of the next non-whitespace character.
65 */
66 protected void parseAndIgnoreWhitespace(final String source,
67 final ParsePosition pos) {
68 parseNextCharacter(source, pos);
69 pos.setIndex(pos.getIndex() - 1);
70 }
71
72 /**
73 * Parses <code>source</code> until a non-whitespace character is found.
74 *
75 * @param source the string to parse
76 * @param pos input/ouput parsing parameter.
77 * @return the first non-whitespace character.
78 */
79 protected char parseNextCharacter(final String source,
80 final ParsePosition pos) {
81 int index = pos.getIndex();
82 final int n = source.length();
83 char ret = 0;
84
85 if (index < n) {
86 char c;
87 do {
88 c = source.charAt(index++);
89 } while (Character.isWhitespace(c) && index < n);
90 pos.setIndex(index);
91
92 if (index < n) {
93 ret = c;
94 }
95 }
96
97 return ret;
98 }
99
100 /**
101 * Parses <code>source</code> for special double values. These values
102 * include Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY.
103 *
104 * @param source the string to parse
105 * @param value the special value to parse.
106 * @param pos input/ouput parsing parameter.
107 * @return the special number.
108 */
109 private Number parseNumber(final String source, final double value,
110 final ParsePosition pos) {
111 Number ret = null;
112
113 StringBuffer sb = new StringBuffer();
114 sb.append('(');
115 sb.append(value);
116 sb.append(')');
117
118 final int n = sb.length();
119 final int startIndex = pos.getIndex();
120 final int endIndex = startIndex + n;
121 if (endIndex < source.length()) {
122 if (source.substring(startIndex, endIndex).compareTo(sb.toString()) == 0) {
123 ret = Double.valueOf(value);
124 pos.setIndex(endIndex);
125 }
126 }
127
128 return ret;
129 }
130
131 /**
132 * Parses <code>source</code> for a number. This method can parse normal,
133 * numeric values as well as special values. These special values include
134 * Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY.
135 *
136 * @param source the string to parse
137 * @param format the number format used to parse normal, numeric values.
138 * @param pos input/ouput parsing parameter.
139 * @return the parsed number.
140 */
141 protected Number parseNumber(final String source, final NumberFormat format,
142 final ParsePosition pos) {
143 final int startIndex = pos.getIndex();
144 Number number = format.parse(source, pos);
145 final int endIndex = pos.getIndex();
146
147 // check for error parsing number
148 if (startIndex == endIndex) {
149 // try parsing special numbers
150 final double[] special = {
151 Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY
152 };
153 for (int i = 0; i < special.length; ++i) {
154 number = parseNumber(source, special[i], pos);
155 if (number != null) {
156 break;
157 }
158 }
159 }
160
161 return number;
162 }
163
164 /**
165 * Parse <code>source</code> for an expected fixed string.
166 * @param source the string to parse
167 * @param expected expected string
168 * @param pos input/ouput parsing parameter.
169 * @return true if the expected string was there
170 */
171 protected boolean parseFixedstring(final String source, final String expected,
172 final ParsePosition pos) {
173
174 final int startIndex = pos.getIndex();
175 final int endIndex = startIndex + expected.length();
176 if ((startIndex >= source.length()) ||
177 (endIndex > source.length()) ||
178 (source.substring(startIndex, endIndex).compareTo(expected) != 0)) {
179 // set index back to start, error index should be the start index
180 pos.setIndex(startIndex);
181 pos.setErrorIndex(startIndex);
182 return false;
183 }
184
185 // the string was here
186 pos.setIndex(endIndex);
187 return true;
188
189 }
190
191 /**
192 * Formats a double value to produce a string. In general, the value is
193 * formatted using the formatting rules of <code>format</code>. There are
194 * three exceptions to this:
195 * <ol>
196 * <li>NaN is formatted as '(NaN)'</li>
197 * <li>Positive infinity is formatted as '(Infinity)'</li>
198 * <li>Negative infinity is formatted as '(-Infinity)'</li>
199 * </ol>
200 *
201 * @param value the double to format.
202 * @param format the format used.
203 * @param toAppendTo where the text is to be appended
204 * @param pos On input: an alignment field, if desired. On output: the
205 * offsets of the alignment field
206 * @return the value passed in as toAppendTo.
207 */
208 protected StringBuffer formatDouble(final double value, final NumberFormat format,
209 final StringBuffer toAppendTo,
210 final FieldPosition pos) {
211 if( Double.isNaN(value) || Double.isInfinite(value) ) {
212 toAppendTo.append('(');
213 toAppendTo.append(value);
214 toAppendTo.append(')');
215 } else {
216 format.format(value, toAppendTo, pos);
217 }
218 return toAppendTo;
219 }
220
221 }