View Javadoc

    1   /*
    2    * Copyright 2004 The Apache Software Foundation.
    3    *
    4    * Licensed under the Apache License, Version 2.0 (the "License");
    5    * you may not use this file except in compliance with the License.
    6    * You may obtain a copy of the License at
    7    *
    8    *      http://www.apache.org/licenses/LICENSE-2.0
    9    *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    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: 1.10 $ $Date: 2004/09/21 04:45:55 $
    34   */
    35  public class ComplexFormat extends Format implements Serializable {
    36
    37      /*** Serializable version identifier */
    38      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         // format real
    142         double re = complex.getReal();
    143         formatDouble(re, getRealFormat(), toAppendTo, pos);
    144
    145         // format sign and imaginary
    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         // parse whitespace
    323         parseAndIgnoreWhitespace(source, pos);
    324
    325         // parse real
    326         Number re = parseNumber(source, getRealFormat(), pos);
    327         if (re == null) {
    328             // invalid real number
    329             // set index back to initial, error index should already be set
    330             // character examined.
    331             pos.setIndex(initialIndex);
    332             return null;
    333         }
    334
    335         // parse sign
    336         int startIndex = pos.getIndex();
    337         char c = parseNextCharacter(source, pos);
    338         int sign = 0;
    339         switch (c) {
    340         case 0 :
    341             // no sign
    342             // return real only complex number
    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             // invalid sign
    352             // set index back to initial, error index should be the last
    353             // character examined.
    354             pos.setIndex(initialIndex);
    355             pos.setErrorIndex(startIndex);
    356             return null;
    357         }
    358
    359         // parse whitespace
    360         parseAndIgnoreWhitespace(source, pos);
    361
    362         // parse imaginary
    363         Number im = parseNumber(source, getRealFormat(), pos);
    364         if (im == null) {
    365             // invalid imaginary number
    366             // set index back to initial, error index should already be set
    367             // character examined.
    368             pos.setIndex(initialIndex);
    369             return null;
    370         }
    371
    372         // parse imaginary character
    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             // set index back to initial, error index should be the start index
    379             // character examined.
    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         // check for error parsing number
    474         if (startIndex == endIndex) {
    475             // try parsing special numbers
    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 }