Coverage Report - org.apache.commons.math.complex.ComplexFormat

Classes in this File Line Coverage Branch Coverage Complexity
ComplexFormat
86% 
95% 
2.179

 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$ $Date: 2005-06-26 15:20:57 -0700 (Sun, 26 Jun 2005) $
 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  28
         this(DEFAULT_IMAGINARY_CHARACTER, getDefaultNumberFormat());
 58  28
     }
 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  284
         this(DEFAULT_IMAGINARY_CHARACTER, format);
 67  284
     }
 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  0
         this(DEFAULT_IMAGINARY_CHARACTER, realFormat, imaginaryFormat);
 78  0
     }
 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  0
         this(imaginaryCharacter, getDefaultNumberFormat());
 87  0
     }
 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  312
         this(imaginaryCharacter, format, (NumberFormat)format.clone());
 97  312
     }
 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  312
         super();
 110  312
         setImaginaryCharacter(imaginaryCharacter);
 111  312
         setImaginaryFormat(imaginaryFormat);
 112  312
         setRealFormat(realFormat);
 113  312
     }
 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  4
         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  56
         pos.setBeginIndex(0);
 139  56
         pos.setEndIndex(0);
 140  
 
 141  
         // format real
 142  56
         double re = complex.getReal();
 143  56
         formatDouble(re, getRealFormat(), toAppendTo, pos);
 144  
         
 145  
         // format sign and imaginary
 146  56
         double im = complex.getImaginary();
 147  56
         if (im < 0.0) {
 148  20
             toAppendTo.append(" - ");
 149  20
             formatDouble(-im, getImaginaryFormat(), toAppendTo, pos);
 150  20
             toAppendTo.append(getImaginaryCharacter());
 151  36
         } else if (im > 0.0 || Double.isNaN(im)) {
 152  28
             toAppendTo.append(" + ");
 153  28
             formatDouble(im, getImaginaryFormat(), toAppendTo, pos);
 154  28
             toAppendTo.append(getImaginaryCharacter());
 155  
         }
 156  
         
 157  56
         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  60
         StringBuffer ret = null;
 177  
         
 178  60
         if (obj instanceof Complex) {
 179  52
             ret = format( (Complex)obj, toAppendTo, pos);
 180  8
         } else if (obj instanceof Number) {
 181  4
             ret = format( new Complex(((Number)obj).doubleValue(), 0.0),
 182  
                 toAppendTo, pos);
 183  
         } else { 
 184  4
             throw new IllegalArgumentException(
 185  
                 "Cannot format given Object as a Date");
 186  
         }
 187  
         
 188  56
         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  104
         if( Double.isNaN(value) || Double.isInfinite(value) ) {
 211  24
             toAppendTo.append('(');
 212  24
             toAppendTo.append(value);
 213  24
             toAppendTo.append(')');
 214  
         } else {
 215  80
             getRealFormat().format(value, toAppendTo, pos);
 216  
         }
 217  104
         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  0
         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  28
         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  308
         NumberFormat nf = NumberFormat.getInstance(locale);
 248  308
         nf.setMaximumFractionDigits(2);
 249  308
         return nf;
 250  
     }
 251  
     
 252  
     /**
 253  
      * Access the imaginaryCharacter.
 254  
      * @return the imaginaryCharacter.
 255  
      */
 256  
     public String getImaginaryCharacter() {
 257  136
         return imaginaryCharacter;
 258  
     }
 259  
     
 260  
     /**
 261  
      * Access the imaginaryFormat.
 262  
      * @return the imaginaryFormat.
 263  
      */
 264  
     public NumberFormat getImaginaryFormat() {
 265  56
         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  4
         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  280
         NumberFormat f = getDefaultNumberFormat(locale);
 283  280
         return new ComplexFormat(f);
 284  
     }
 285  
     
 286  
     /**
 287  
      * Access the realFormat.
 288  
      * @return the realFormat.
 289  
      */
 290  
     public NumberFormat getRealFormat() {
 291  332
         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  0
         ParsePosition parsePosition = new ParsePosition(0);
 304  0
         Complex result = parse(source, parsePosition);
 305  0
         if (parsePosition.getIndex() == 0) {
 306  0
             throw new ParseException("Unparseable complex number: \"" + source +
 307  
                 "\"", parsePosition.getErrorIndex());
 308  
         }
 309  0
         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  48
         int initialIndex = pos.getIndex();
 321  
 
 322  
         // parse whitespace
 323  48
         parseAndIgnoreWhitespace(source, pos);
 324  
 
 325  
         // parse real
 326  48
         Number re = parseNumber(source, getRealFormat(), pos);
 327  48
         if (re == null) {
 328  
             // invalid real number
 329  
             // set index back to initial, error index should already be set
 330  
             // character examined.
 331  0
             pos.setIndex(initialIndex);
 332  0
             return null;
 333  
         }
 334  
 
 335  
         // parse sign
 336  48
         int startIndex = pos.getIndex();
 337  48
         char c = parseNextCharacter(source, pos);
 338  48
         int sign = 0;
 339  48
         switch (c) {
 340  
         case 0 :
 341  
             // no sign
 342  
             // return real only complex number
 343  4
             return new Complex(re.doubleValue(), 0.0);
 344  
         case '-' :
 345  20
             sign = -1;
 346  20
             break;
 347  
         case '+' :
 348  24
             sign = 1;
 349  24
             break;
 350  
         default :
 351  
             // invalid sign
 352  
             // set index back to initial, error index should be the last
 353  
             // character examined.
 354  0
             pos.setIndex(initialIndex);
 355  0
             pos.setErrorIndex(startIndex);
 356  0
             return null;
 357  
         }
 358  
 
 359  
         // parse whitespace
 360  44
         parseAndIgnoreWhitespace(source, pos);
 361  
 
 362  
         // parse imaginary
 363  44
         Number im = parseNumber(source, getRealFormat(), pos);
 364  44
         if (im == null) {
 365  
             // invalid imaginary number
 366  
             // set index back to initial, error index should already be set
 367  
             // character examined.
 368  0
             pos.setIndex(initialIndex);
 369  0
             return null;
 370  
         }
 371  
 
 372  
         // parse imaginary character
 373  44
         int n = getImaginaryCharacter().length();
 374  44
         startIndex = pos.getIndex();
 375  44
         int endIndex = startIndex + n;
 376  44
         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  0
             pos.setIndex(initialIndex);
 381  0
             pos.setErrorIndex(startIndex);
 382  0
             return null;
 383  
         }
 384  44
         pos.setIndex(endIndex);
 385  
 
 386  44
         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  92
         parseNextCharacter(source, pos);
 398  92
         pos.setIndex(pos.getIndex() - 1);
 399  92
     }
 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  140
          int index = pos.getIndex();
 410  140
          int n = source.length();
 411  140
          char ret = 0;
 412  
 
 413  140
          if (index < n) {
 414  
              char c;
 415  
              do {
 416  224
                  c = source.charAt(index++);
 417  224
              } while (Character.isWhitespace(c) && index < n);
 418  136
              pos.setIndex(index);
 419  
          
 420  136
              if (index < n) {
 421  136
                  ret = c;
 422  
              }
 423  
          }
 424  
          
 425  140
          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  44
         Number ret = null;
 439  
         
 440  44
         StringBuffer sb = new StringBuffer();
 441  44
         sb.append('(');
 442  44
         sb.append(value);
 443  44
         sb.append(')');
 444  
         
 445  44
         int n = sb.length();
 446  44
         int startIndex = pos.getIndex();
 447  44
         int endIndex = startIndex + n;
 448  44
         if (endIndex < source.length()) {
 449  44
             if (source.substring(startIndex, endIndex).compareTo(sb.toString()) == 0) {
 450  24
                 ret = new Double(value);
 451  24
                 pos.setIndex(endIndex);
 452  
             }
 453  
         }
 454  
         
 455  44
         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  92
         int startIndex = pos.getIndex();
 470  92
         Number number = getRealFormat().parse(source, pos);
 471  92
         int endIndex = pos.getIndex();
 472  
         
 473  
         // check for error parsing number
 474  92
         if (startIndex == endIndex) {
 475  
             // try parsing special numbers
 476  24
             double[] special = {Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY};
 477  44
             for (int i = 0; i < special.length; ++i) {
 478  44
                 number = parseNumber(source, special[i], pos);
 479  44
                 if (number != null) {
 480  24
                     break;
 481  
                 }
 482  
             }
 483  
         }
 484  
         
 485  92
         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  48
         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  456
         if (imaginaryCharacter == null || imaginaryCharacter.length() == 0) {
 507  8
             throw new IllegalArgumentException(
 508  
                 "imaginaryCharacter must be a non-empty string.");
 509  
         }
 510  448
         this.imaginaryCharacter = imaginaryCharacter;
 511  448
     }
 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  320
         if (imaginaryFormat == null) {
 521  4
             throw new IllegalArgumentException(
 522  
                 "imaginaryFormat can not be null.");
 523  
         }
 524  316
         this.imaginaryFormat = imaginaryFormat;
 525  316
     }
 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  320
         if (realFormat == null) {
 535  4
             throw new IllegalArgumentException(
 536  
                 "realFormat can not be null.");
 537  
         }
 538  316
         this.realFormat = realFormat;
 539  316
     }
 540  
 }