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