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    }