1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.math.complex;
18
19 import java.io.Serializable;
20 import java.text.FieldPosition;
21 import java.text.Format;
22 import java.text.NumberFormat;
23 import java.text.ParseException;
24 import java.text.ParsePosition;
25 import java.util.Locale;
26
27 /**
28 * Formats a Complex number in cartesian format "Re(c) + Im(c)i". 'i' can
29 * be replaced with 'j', and the number format for both real and imaginary parts
30 * can be configured.
31 *
32 * @author Apache Software Foundation
33 * @version $Revision: 348519 $ $Date: 2005-11-23 12:12:18 -0700 (Wed, 23 Nov 2005) $
34 */
35 public class ComplexFormat extends Format implements Serializable {
36
37 /** Serializable version identifier */
38 private static final long serialVersionUID = -6337346779577272306L;
39
40 /** The default imaginary character. */
41 private static final String DEFAULT_IMAGINARY_CHARACTER = "i";
42
43 /** The notation used to signify the imaginary part of the complex number. */
44 private String imaginaryCharacter;
45
46 /** The format used for the imaginary part. */
47 private NumberFormat imaginaryFormat;
48
49 /** The format used for the real part. */
50 private NumberFormat realFormat;
51
52 /**
53 * Create an instance with the default imaginary character, 'i', and the
54 * default number format for both real and imaginary parts.
55 */
56 public ComplexFormat() {
57 this(DEFAULT_IMAGINARY_CHARACTER, getDefaultNumberFormat());
58 }
59
60 /**
61 * Create an instance with a custom number format for both real and
62 * imaginary parts.
63 * @param format the custom format for both real and imaginary parts.
64 */
65 public ComplexFormat(NumberFormat format) {
66 this(DEFAULT_IMAGINARY_CHARACTER, format);
67 }
68
69 /**
70 * Create an instance with a custom number format for the real part and a
71 * custom number format for the imaginary part.
72 * @param realFormat the custom format for the real part.
73 * @param imaginaryFormat the custom format for the imaginary part.
74 */
75 public ComplexFormat(NumberFormat realFormat,
76 NumberFormat imaginaryFormat) {
77 this(DEFAULT_IMAGINARY_CHARACTER, realFormat, imaginaryFormat);
78 }
79
80 /**
81 * Create an instance with a custom imaginary character, and the default
82 * number format for both real and imaginary parts.
83 * @param imaginaryCharacter The custom imaginary character.
84 */
85 public ComplexFormat(String imaginaryCharacter) {
86 this(imaginaryCharacter, getDefaultNumberFormat());
87 }
88
89 /**
90 * Create an instance with a custom imaginary character, and a custom number
91 * format for both real and imaginary parts.
92 * @param imaginaryCharacter The custom imaginary character.
93 * @param format the custom format for both real and imaginary parts.
94 */
95 public ComplexFormat(String imaginaryCharacter, NumberFormat format) {
96 this(imaginaryCharacter, format, (NumberFormat)format.clone());
97 }
98
99 /**
100 * Create an instance with a custom imaginary character, a custom number
101 * format for the real part, and a custom number format for the imaginary
102 * part.
103 * @param imaginaryCharacter The custom imaginary character.
104 * @param realFormat the custom format for the real part.
105 * @param imaginaryFormat the custom format for the imaginary part.
106 */
107 public ComplexFormat(String imaginaryCharacter, NumberFormat realFormat,
108 NumberFormat imaginaryFormat) {
109 super();
110 setImaginaryCharacter(imaginaryCharacter);
111 setImaginaryFormat(imaginaryFormat);
112 setRealFormat(realFormat);
113 }
114
115 /**
116 * This static method calls formatComplex() on a default instance of
117 * ComplexFormat.
118 *
119 * @param c Complex object to format
120 * @return A formatted number in the form "Re(c) + Im(c)i"
121 */
122 public static String formatComplex( Complex c ) {
123 return getInstance().format( c );
124 }
125
126 /**
127 * Formats a {@link Complex} object to produce a string.
128 *
129 * @param complex the object to format.
130 * @param toAppendTo where the text is to be appended
131 * @param pos On input: an alignment field, if desired. On output: the
132 * offsets of the alignment field
133 * @return the value passed in as toAppendTo.
134 */
135 public StringBuffer format(Complex complex, StringBuffer toAppendTo,
136 FieldPosition pos) {
137
138 pos.setBeginIndex(0);
139 pos.setEndIndex(0);
140
141
142 double re = complex.getReal();
143 formatDouble(re, getRealFormat(), toAppendTo, pos);
144
145
146 double im = complex.getImaginary();
147 if (im < 0.0) {
148 toAppendTo.append(" - ");
149 formatDouble(-im, getImaginaryFormat(), toAppendTo, pos);
150 toAppendTo.append(getImaginaryCharacter());
151 } else if (im > 0.0 || Double.isNaN(im)) {
152 toAppendTo.append(" + ");
153 formatDouble(im, getImaginaryFormat(), toAppendTo, pos);
154 toAppendTo.append(getImaginaryCharacter());
155 }
156
157 return toAppendTo;
158 }
159
160 /**
161 * Formats a object to produce a string. <code>obj</code> must be either a
162 * {@link Complex} object or a {@link Number} object. Any other type of
163 * object will result in an {@link IllegalArgumentException} being thrown.
164 *
165 * @param obj the object to format.
166 * @param toAppendTo where the text is to be appended
167 * @param pos On input: an alignment field, if desired. On output: the
168 * offsets of the alignment field
169 * @return the value passed in as toAppendTo.
170 * @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition)
171 * @throws IllegalArgumentException is <code>obj</code> is not a valid type.
172 */
173 public StringBuffer format(Object obj, StringBuffer toAppendTo,
174 FieldPosition pos) {
175
176 StringBuffer ret = null;
177
178 if (obj instanceof Complex) {
179 ret = format( (Complex)obj, toAppendTo, pos);
180 } else if (obj instanceof Number) {
181 ret = format( new Complex(((Number)obj).doubleValue(), 0.0),
182 toAppendTo, pos);
183 } else {
184 throw new IllegalArgumentException(
185 "Cannot format given Object as a Date");
186 }
187
188 return ret;
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 private StringBuffer formatDouble(double value, NumberFormat format,
209 StringBuffer toAppendTo, FieldPosition pos) {
210 if( Double.isNaN(value) || Double.isInfinite(value) ) {
211 toAppendTo.append('(');
212 toAppendTo.append(value);
213 toAppendTo.append(')');
214 } else {
215 getRealFormat().format(value, toAppendTo, pos);
216 }
217 return toAppendTo;
218 }
219
220 /**
221 * Get the set of locales for which complex formats are available. This
222 * is the same set as the {@link NumberFormat} set.
223 * @return available complex format locales.
224 */
225 public static Locale[] getAvailableLocales() {
226 return NumberFormat.getAvailableLocales();
227 }
228
229 /**
230 * Create a default number format. The default number format is based on
231 * {@link NumberFormat#getInstance()} with the only customizing is the
232 * maximum number of fraction digits, which is set to 2.
233 * @return the default number format.
234 */
235 private static NumberFormat getDefaultNumberFormat() {
236 return getDefaultNumberFormat(Locale.getDefault());
237 }
238
239 /**
240 * Create a default number format. The default number format is based on
241 * {@link NumberFormat#getInstance(java.util.Locale)} with the only
242 * customizing is the maximum number of fraction digits, which is set to 2.
243 * @param locale the specific locale used by the format.
244 * @return the default number format specific to the given locale.
245 */
246 private static NumberFormat getDefaultNumberFormat(Locale locale) {
247 NumberFormat nf = NumberFormat.getInstance(locale);
248 nf.setMaximumFractionDigits(2);
249 return nf;
250 }
251
252 /**
253 * Access the imaginaryCharacter.
254 * @return the imaginaryCharacter.
255 */
256 public String getImaginaryCharacter() {
257 return imaginaryCharacter;
258 }
259
260 /**
261 * Access the imaginaryFormat.
262 * @return the imaginaryFormat.
263 */
264 public NumberFormat getImaginaryFormat() {
265 return imaginaryFormat;
266 }
267
268 /**
269 * Returns the default complex format for the current locale.
270 * @return the default complex format.
271 */
272 public static ComplexFormat getInstance() {
273 return getInstance(Locale.getDefault());
274 }
275
276 /**
277 * Returns the default complex format for the given locale.
278 * @param locale the specific locale used by the format.
279 * @return the complex format specific to the given locale.
280 */
281 public static ComplexFormat getInstance(Locale locale) {
282 NumberFormat f = getDefaultNumberFormat(locale);
283 return new ComplexFormat(f);
284 }
285
286 /**
287 * Access the realFormat.
288 * @return the realFormat.
289 */
290 public NumberFormat getRealFormat() {
291 return realFormat;
292 }
293
294 /**
295 * Parses a string to produce a {@link Complex} object.
296 *
297 * @param source the string to parse
298 * @return the parsed {@link Complex} object.
299 * @exception ParseException if the beginning of the specified string
300 * cannot be parsed.
301 */
302 public Complex parse(String source) throws ParseException {
303 ParsePosition parsePosition = new ParsePosition(0);
304 Complex result = parse(source, parsePosition);
305 if (parsePosition.getIndex() == 0) {
306 throw new ParseException("Unparseable complex number: \"" + source +
307 "\"", parsePosition.getErrorIndex());
308 }
309 return result;
310 }
311
312 /**
313 * Parses a string to produce a {@link Complex} object.
314 *
315 * @param source the string to parse
316 * @param pos input/ouput parsing parameter.
317 * @return the parsed {@link Complex} object.
318 */
319 public Complex parse(String source, ParsePosition pos) {
320 int initialIndex = pos.getIndex();
321
322
323 parseAndIgnoreWhitespace(source, pos);
324
325
326 Number re = parseNumber(source, getRealFormat(), pos);
327 if (re == null) {
328
329
330
331 pos.setIndex(initialIndex);
332 return null;
333 }
334
335
336 int startIndex = pos.getIndex();
337 char c = parseNextCharacter(source, pos);
338 int sign = 0;
339 switch (c) {
340 case 0 :
341
342
343 return new Complex(re.doubleValue(), 0.0);
344 case '-' :
345 sign = -1;
346 break;
347 case '+' :
348 sign = 1;
349 break;
350 default :
351
352
353
354 pos.setIndex(initialIndex);
355 pos.setErrorIndex(startIndex);
356 return null;
357 }
358
359
360 parseAndIgnoreWhitespace(source, pos);
361
362
363 Number im = parseNumber(source, getRealFormat(), pos);
364 if (im == null) {
365
366
367
368 pos.setIndex(initialIndex);
369 return null;
370 }
371
372
373 int n = getImaginaryCharacter().length();
374 startIndex = pos.getIndex();
375 int endIndex = startIndex + n;
376 if (source.substring(startIndex, endIndex).compareTo(
377 getImaginaryCharacter()) != 0) {
378
379
380 pos.setIndex(initialIndex);
381 pos.setErrorIndex(startIndex);
382 return null;
383 }
384 pos.setIndex(endIndex);
385
386 return new Complex(re.doubleValue(), im.doubleValue() * sign);
387 }
388
389 /**
390 * Parses <code>source</code> until a non-whitespace character is found.
391 *
392 * @param source the string to parse
393 * @param pos input/ouput parsing parameter. On output, <code>pos</code>
394 * holds the index of the next non-whitespace character.
395 */
396 private void parseAndIgnoreWhitespace(String source, ParsePosition pos) {
397 parseNextCharacter(source, pos);
398 pos.setIndex(pos.getIndex() - 1);
399 }
400
401 /**
402 * Parses <code>source</code> until a non-whitespace character is found.
403 *
404 * @param source the string to parse
405 * @param pos input/ouput parsing parameter.
406 * @return the first non-whitespace character.
407 */
408 private char parseNextCharacter(String source, ParsePosition pos) {
409 int index = pos.getIndex();
410 int n = source.length();
411 char ret = 0;
412
413 if (index < n) {
414 char c;
415 do {
416 c = source.charAt(index++);
417 } while (Character.isWhitespace(c) && index < n);
418 pos.setIndex(index);
419
420 if (index < n) {
421 ret = c;
422 }
423 }
424
425 return ret;
426 }
427
428 /**
429 * Parses <code>source</code> for a special double values. These values
430 * include Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY.
431 *
432 * @param source the string to parse
433 * @param value the special value to parse.
434 * @param pos input/ouput parsing parameter.
435 * @return the special number.
436 */
437 private Number parseNumber(String source, double value, ParsePosition pos) {
438 Number ret = null;
439
440 StringBuffer sb = new StringBuffer();
441 sb.append('(');
442 sb.append(value);
443 sb.append(')');
444
445 int n = sb.length();
446 int startIndex = pos.getIndex();
447 int endIndex = startIndex + n;
448 if (endIndex < source.length()) {
449 if (source.substring(startIndex, endIndex).compareTo(sb.toString()) == 0) {
450 ret = new Double(value);
451 pos.setIndex(endIndex);
452 }
453 }
454
455 return ret;
456 }
457
458 /**
459 * Parses <code>source</code> for a number. This method can parse normal,
460 * numeric values as well as special values. These special values include
461 * Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY.
462 *
463 * @param source the string to parse
464 * @param format the number format used to parse normal, numeric values.
465 * @param pos input/ouput parsing parameter.
466 * @return the parsed number.
467 */
468 private Number parseNumber(String source, NumberFormat format, ParsePosition pos) {
469 int startIndex = pos.getIndex();
470 Number number = getRealFormat().parse(source, pos);
471 int endIndex = pos.getIndex();
472
473
474 if (startIndex == endIndex) {
475
476 double[] special = {Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY};
477 for (int i = 0; i < special.length; ++i) {
478 number = parseNumber(source, special[i], pos);
479 if (number != null) {
480 break;
481 }
482 }
483 }
484
485 return number;
486 }
487
488 /**
489 * Parses a string to produce a object.
490 *
491 * @param source the string to parse
492 * @param pos input/ouput parsing parameter.
493 * @return the parsed object.
494 * @see java.text.Format#parseObject(java.lang.String, java.text.ParsePosition)
495 */
496 public Object parseObject(String source, ParsePosition pos) {
497 return parse(source, pos);
498 }
499 /**
500 * Modify the imaginaryCharacter.
501 * @param imaginaryCharacter The new imaginaryCharacter value.
502 * @throws IllegalArgumentException if <code>imaginaryCharacter</code> is
503 * <code>null</code> or an empty string.
504 */
505 public void setImaginaryCharacter(String imaginaryCharacter) {
506 if (imaginaryCharacter == null || imaginaryCharacter.length() == 0) {
507 throw new IllegalArgumentException(
508 "imaginaryCharacter must be a non-empty string.");
509 }
510 this.imaginaryCharacter = imaginaryCharacter;
511 }
512
513 /**
514 * Modify the imaginaryFormat.
515 * @param imaginaryFormat The new imaginaryFormat value.
516 * @throws IllegalArgumentException if <code>imaginaryFormat</code> is
517 * <code>null</code>.
518 */
519 public void setImaginaryFormat(NumberFormat imaginaryFormat) {
520 if (imaginaryFormat == null) {
521 throw new IllegalArgumentException(
522 "imaginaryFormat can not be null.");
523 }
524 this.imaginaryFormat = imaginaryFormat;
525 }
526
527 /**
528 * Modify the realFormat.
529 * @param realFormat The new realFormat value.
530 * @throws IllegalArgumentException if <code>realFormat</code> is
531 * <code>null</code>.
532 */
533 public void setRealFormat(NumberFormat realFormat) {
534 if (realFormat == null) {
535 throw new IllegalArgumentException(
536 "realFormat can not be null.");
537 }
538 this.realFormat = realFormat;
539 }
540 }