    001    /*
    002     * Licensed to the Apache Software Foundation (ASF) under one or more
    003     * contributor license agreements.  See the NOTICE file distributed with
    004     * this work for additional information regarding copyright ownership.
    005     * The ASF licenses this file to You under the Apache License, Version 2.0
    006     * (the "License"); you may not use this file except in compliance with
    007     * the License.  You may obtain a copy of the License at
    008     *
    009     *      http://www.apache.org/licenses/LICENSE-2.0
    010     *
    011     * Unless required by applicable law or agreed to in writing, software
    012     * distributed under the License is distributed on an "AS IS" BASIS,
    013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014     * See the License for the specific language governing permissions and
    015     * limitations under the License.
    016     */
    017
    018    package org.apache.commons.math3.complex;
    019
    020    import java.text.FieldPosition;
    021    import java.text.NumberFormat;
    022    import java.text.ParsePosition;
    023    import java.util.Locale;
    024
    025    import org.apache.commons.math3.exception.MathIllegalArgumentException;
    026    import org.apache.commons.math3.exception.MathParseException;
    027    import org.apache.commons.math3.exception.NoDataException;
    028    import org.apache.commons.math3.exception.NullArgumentException;
    029    import org.apache.commons.math3.exception.util.LocalizedFormats;
    030    import org.apache.commons.math3.util.CompositeFormat;
    031
    032    /**
    033     * Formats a Complex number in cartesian format "Re(c) + Im(c)i".  'i' can
    034     * be replaced with 'j' (or anything else), and the number format for both real
    035     * and imaginary parts can be configured.
    036     *
    037     * @version $Id: ComplexFormat.java 1416643 2012-12-03 19:37:14Z tn $
    038     */
    039    public class ComplexFormat {
    040
    041         /** The default imaginary character. */
    042        private static final String DEFAULT_IMAGINARY_CHARACTER = "i";
    043        /** The notation used to signify the imaginary part of the complex number. */
    044        private final String imaginaryCharacter;
    045        /** The format used for the imaginary part. */
    046        private final NumberFormat imaginaryFormat;
    047        /** The format used for the real part. */
    048        private final NumberFormat realFormat;
    049
    050        /**
    051         * Create an instance with the default imaginary character, 'i', and the
    052         * default number format for both real and imaginary parts.
    053         */
    054        public ComplexFormat() {
    055            this.imaginaryCharacter = DEFAULT_IMAGINARY_CHARACTER;
    056            this.imaginaryFormat = CompositeFormat.getDefaultNumberFormat();
    057            this.realFormat = imaginaryFormat;
    058        }
    059
    060        /**
    061         * Create an instance with a custom number format for both real and
    062         * imaginary parts.
    063         * @param format the custom format for both real and imaginary parts.
    064         * @throws NullArgumentException if {@code realFormat} is {@code null}.
    065         */
    066        public ComplexFormat(NumberFormat format) throws NullArgumentException {
    067            if (format == null) {
    068                throw new NullArgumentException(LocalizedFormats.IMAGINARY_FORMAT);
    069            }
    070            this.imaginaryCharacter = DEFAULT_IMAGINARY_CHARACTER;
    071            this.imaginaryFormat = format;
    072            this.realFormat = format;
    073        }
    074
    075        /**
    076         * Create an instance with a custom number format for the real part and a
    077         * custom number format for the imaginary part.
    078         * @param realFormat the custom format for the real part.
    079         * @param imaginaryFormat the custom format for the imaginary part.
    080         * @throws NullArgumentException if {@code imaginaryFormat} is {@code null}.
    081         * @throws NullArgumentException if {@code realFormat} is {@code null}.
    082          */
    083        public ComplexFormat(NumberFormat realFormat, NumberFormat imaginaryFormat)
    084            throws NullArgumentException {
    085            if (imaginaryFormat == null) {
    086                throw new NullArgumentException(LocalizedFormats.IMAGINARY_FORMAT);
    087            }
    088            if (realFormat == null) {
    089                throw new NullArgumentException(LocalizedFormats.REAL_FORMAT);
    090            }
    091
    092            this.imaginaryCharacter = DEFAULT_IMAGINARY_CHARACTER;
    093            this.imaginaryFormat = imaginaryFormat;
    094            this.realFormat = realFormat;
    095        }
    096
    097        /**
    098         * Create an instance with a custom imaginary character, and the default
    099         * number format for both real and imaginary parts.
    100         * @param imaginaryCharacter The custom imaginary character.
    101         * @throws NullArgumentException if {@code imaginaryCharacter} is
    102         * {@code null}.
    103         * @throws NoDataException if {@code imaginaryCharacter} is an
    104         * empty string.
    105         */
    106        public ComplexFormat(String imaginaryCharacter)
    107            throws NullArgumentException, NoDataException {
    108            this(imaginaryCharacter, CompositeFormat.getDefaultNumberFormat());
    109        }
    110
    111        /**
    112         * Create an instance with a custom imaginary character, and a custom number
    113         * format for both real and imaginary parts.
    114         * @param imaginaryCharacter The custom imaginary character.
    115         * @param format the custom format for both real and imaginary parts.
    116         * @throws NullArgumentException if {@code imaginaryCharacter} is
    117         * {@code null}.
    118         * @throws NoDataException if {@code imaginaryCharacter} is an
    119         * empty string.
    120         * @throws NullArgumentException if {@code format} is {@code null}.
    121         */
    122        public ComplexFormat(String imaginaryCharacter, NumberFormat format)
    123            throws NullArgumentException, NoDataException {
    124            this(imaginaryCharacter, format, format);
    125        }
    126
    127        /**
    128         * Create an instance with a custom imaginary character, a custom number
    129         * format for the real part, and a custom number format for the imaginary
    130         * part.
    131         *
    132         * @param imaginaryCharacter The custom imaginary character.
    133         * @param realFormat the custom format for the real part.
    134         * @param imaginaryFormat the custom format for the imaginary part.
    135         * @throws NullArgumentException if {@code imaginaryCharacter} is
    136         * {@code null}.
    137         * @throws NoDataException if {@code imaginaryCharacter} is an
    138         * empty string.
    139         * @throws NullArgumentException if {@code imaginaryFormat} is {@code null}.
    140         * @throws NullArgumentException if {@code realFormat} is {@code null}.
    141         */
    142        public ComplexFormat(String imaginaryCharacter,
    143                             NumberFormat realFormat,
    144                             NumberFormat imaginaryFormat)
    145            throws NullArgumentException, NoDataException {
    146            if (imaginaryCharacter == null) {
    147                throw new NullArgumentException();
    148            }
    149            if (imaginaryCharacter.length() == 0) {
    150                throw new NoDataException();
    151            }
    152            if (imaginaryFormat == null) {
    153                throw new NullArgumentException(LocalizedFormats.IMAGINARY_FORMAT);
    154            }
    155            if (realFormat == null) {
    156                throw new NullArgumentException(LocalizedFormats.REAL_FORMAT);
    157            }
    158
    159            this.imaginaryCharacter = imaginaryCharacter;
    160            this.imaginaryFormat = imaginaryFormat;
    161            this.realFormat = realFormat;
    162        }
    163
    164        /**
    165         * Get the set of locales for which complex formats are available.
    166         * <p>This is the same set as the {@link NumberFormat} set.</p>
    167         * @return available complex format locales.
    168         */
    169        public static Locale[] getAvailableLocales() {
    170            return NumberFormat.getAvailableLocales();
    171        }
    172
    173        /**
    174         * This method calls {@link #format(Object,StringBuffer,FieldPosition)}.
    175         *
    176         * @param c Complex object to format.
    177         * @return A formatted number in the form "Re(c) + Im(c)i".
    178         */
    179        public String format(Complex c) {
    180            return format(c, new StringBuffer(), new FieldPosition(0)).toString();
    181        }
    182
    183        /**
    184         * This method calls {@link #format(Object,StringBuffer,FieldPosition)}.
    185         *
    186         * @param c Double object to format.
    187         * @return A formatted number.
    188         */
    189        public String format(Double c) {
    190            return format(new Complex(c, 0), new StringBuffer(), new FieldPosition(0)).toString();
    191        }
    192
    193        /**
    194         * Formats a {@link Complex} object to produce a string.
    195         *
    196         * @param complex the object to format.
    197         * @param toAppendTo where the text is to be appended
    198         * @param pos On input: an alignment field, if desired. On output: the
    199         *            offsets of the alignment field
    200         * @return the value passed in as toAppendTo.
    201         */
    202        public StringBuffer format(Complex complex, StringBuffer toAppendTo,
    203                                   FieldPosition pos) {
    204            pos.setBeginIndex(0);
    205            pos.setEndIndex(0);
    206
    207            // format real
    208            double re = complex.getReal();
    209            CompositeFormat.formatDouble(re, getRealFormat(), toAppendTo, pos);
    210
    211            // format sign and imaginary
    212            double im = complex.getImaginary();
    213            StringBuffer imAppendTo;
    214            if (im < 0.0) {
    215                toAppendTo.append(" - ");
    216                imAppendTo = formatImaginary(-im, new StringBuffer(), pos);
    217                toAppendTo.append(imAppendTo);
    218                toAppendTo.append(getImaginaryCharacter());
    219            } else if (im > 0.0 || Double.isNaN(im)) {
    220                toAppendTo.append(" + ");
    221                imAppendTo = formatImaginary(im, new StringBuffer(), pos);
    222                toAppendTo.append(imAppendTo);
    223                toAppendTo.append(getImaginaryCharacter());
    224            }
    225
    226            return toAppendTo;
    227        }
    228
    229        /**
    230         * Format the absolute value of the imaginary part.
    231         *
    232         * @param absIm Absolute value of the imaginary part of a complex number.
    233         * @param toAppendTo where the text is to be appended.
    234         * @param pos On input: an alignment field, if desired. On output: the
    235         * offsets of the alignment field.
    236         * @return the value passed in as toAppendTo.
    237         */
    238        private StringBuffer formatImaginary(double absIm,
    239                                             StringBuffer toAppendTo,
    240                                             FieldPosition pos) {
    241            pos.setBeginIndex(0);
    242            pos.setEndIndex(0);
    243
    244            CompositeFormat.formatDouble(absIm, getImaginaryFormat(), toAppendTo, pos);
    245            if (toAppendTo.toString().equals("1")) {
    246                // Remove the character "1" if it is the only one.
    247                toAppendTo.setLength(0);
    248            }
    249
    250            return toAppendTo;
    251        }
    252
    253        /**
    254         * Formats a object to produce a string.  {@code obj} must be either a
    255         * {@link Complex} object or a {@link Number} object.  Any other type of
    256         * object will result in an {@link IllegalArgumentException} being thrown.
    257         *
    258         * @param obj the object to format.
    259         * @param toAppendTo where the text is to be appended
    260         * @param pos On input: an alignment field, if desired. On output: the
    261         *            offsets of the alignment field
    262         * @return the value passed in as toAppendTo.
    263         * @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition)
    264         * @throws MathIllegalArgumentException is {@code obj} is not a valid type.
    265         */
    266        public StringBuffer format(Object obj, StringBuffer toAppendTo,
    267                                   FieldPosition pos)
    268            throws MathIllegalArgumentException {
    269
    270            StringBuffer ret = null;
    271
    272            if (obj instanceof Complex) {
    273                ret = format( (Complex)obj, toAppendTo, pos);
    274            } else if (obj instanceof Number) {
    275                ret = format(new Complex(((Number)obj).doubleValue(), 0.0),
    276                             toAppendTo, pos);
    277            } else {
    278                throw new MathIllegalArgumentException(LocalizedFormats.CANNOT_FORMAT_INSTANCE_AS_COMPLEX,
    279                                                       obj.getClass().getName());
    280            }
    281
    282            return ret;
    283        }
    284
    285        /**
    286         * Access the imaginaryCharacter.
    287         * @return the imaginaryCharacter.
    288         */
    289        public String getImaginaryCharacter() {
    290            return imaginaryCharacter;
    291        }
    292
    293        /**
    294         * Access the imaginaryFormat.
    295         * @return the imaginaryFormat.
    296         */
    297        public NumberFormat getImaginaryFormat() {
    298            return imaginaryFormat;
    299        }
    300
    301        /**
    302         * Returns the default complex format for the current locale.
    303         * @return the default complex format.
    304         */
    305        public static ComplexFormat getInstance() {
    306            return getInstance(Locale.getDefault());
    307        }
    308
    309        /**
    310         * Returns the default complex format for the given locale.
    311         * @param locale the specific locale used by the format.
    312         * @return the complex format specific to the given locale.
    313         */
    314        public static ComplexFormat getInstance(Locale locale) {
    315            NumberFormat f = CompositeFormat.getDefaultNumberFormat(locale);
    316            return new ComplexFormat(f);
    317        }
    318
    319        /**
    320         * Returns the default complex format for the given locale.
    321         * @param locale the specific locale used by the format.
    322         * @param imaginaryCharacter Imaginary character.
    323         * @return the complex format specific to the given locale.
    324         * @throws NullArgumentException if {@code imaginaryCharacter} is
    325         * {@code null}.
    326         * @throws NoDataException if {@code imaginaryCharacter} is an
    327         * empty string.
    328         */
    329        public static ComplexFormat getInstance(String imaginaryCharacter, Locale locale)
    330            throws NullArgumentException, NoDataException {
    331            NumberFormat f = CompositeFormat.getDefaultNumberFormat(locale);
    332            return new ComplexFormat(imaginaryCharacter, f);
    333        }
    334
    335        /**
    336         * Access the realFormat.
    337         * @return the realFormat.
    338         */
    339        public NumberFormat getRealFormat() {
    340            return realFormat;
    341        }
    342
    343        /**
    344         * Parses a string to produce a {@link Complex} object.
    345         *
    346         * @param source the string to parse.
    347         * @return the parsed {@link Complex} object.
    348         * @throws MathParseException if the beginning of the specified string
    349         * cannot be parsed.
    350         */
    351        public Complex parse(String source) throws MathParseException {
    352            ParsePosition parsePosition = new ParsePosition(0);
    353            Complex result = parse(source, parsePosition);
    354            if (parsePosition.getIndex() == 0) {
    355                throw new MathParseException(source,
    356                                             parsePosition.getErrorIndex(),
    357                                             Complex.class);
    358            }
    359            return result;
    360        }
    361
    362        /**
    363         * Parses a string to produce a {@link Complex} object.
    364         *
    365         * @param source the string to parse
    366         * @param pos input/ouput parsing parameter.
    367         * @return the parsed {@link Complex} object.
    368         */
    369        public Complex parse(String source, ParsePosition pos) {
    370            int initialIndex = pos.getIndex();
    371
    372            // parse whitespace
    373            CompositeFormat.parseAndIgnoreWhitespace(source, pos);
    374
    375            // parse real
    376            Number re = CompositeFormat.parseNumber(source, getRealFormat(), pos);
    377            if (re == null) {
    378                // invalid real number
    379                // set index back to initial, error index should already be set
    380                pos.setIndex(initialIndex);
    381                return null;
    382            }
    383
    384            // parse sign
    385            int startIndex = pos.getIndex();
    386            char c = CompositeFormat.parseNextCharacter(source, pos);
    387            int sign = 0;
    388            switch (c) {
    389            case 0 :
    390                // no sign
    391                // return real only complex number
    392                return new Complex(re.doubleValue(), 0.0);
    393            case '-' :
    394                sign = -1;
    395                break;
    396            case '+' :
    397                sign = 1;
    398                break;
    399            default :
    400                // invalid sign
    401                // set index back to initial, error index should be the last
    402                // character examined.
    403                pos.setIndex(initialIndex);
    404                pos.setErrorIndex(startIndex);
    405                return null;
    406            }
    407
    408            // parse whitespace
    409            CompositeFormat.parseAndIgnoreWhitespace(source, pos);
    410
    411            // parse imaginary
    412            Number im = CompositeFormat.parseNumber(source, getRealFormat(), pos);
    413            if (im == null) {
    414                // invalid imaginary number
    415                // set index back to initial, error index should already be set
    416                pos.setIndex(initialIndex);
    417                return null;
    418            }
    419
    420            // parse imaginary character
    421            if (!CompositeFormat.parseFixedstring(source, getImaginaryCharacter(), pos)) {
    422                return null;
    423            }
    424
    425            return new Complex(re.doubleValue(), im.doubleValue() * sign);
    426
    427        }
    428    }