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.math4.legacy.util;
19  
20  import java.text.FieldPosition;
21  import java.text.NumberFormat;
22  import java.text.ParsePosition;
23  import java.util.Locale;
24  
25  import org.apache.commons.numbers.complex.Complex;
26  import org.apache.commons.math4.legacy.exception.MathIllegalArgumentException;
27  import org.apache.commons.math4.legacy.exception.MathParseException;
28  import org.apache.commons.math4.legacy.exception.NoDataException;
29  import org.apache.commons.math4.legacy.exception.NullArgumentException;
30  import org.apache.commons.math4.legacy.exception.util.LocalizedFormats;
31  
32  /**
33   * Formats a Complex number in cartesian format "Re(c) + Im(c)i".  'i' can
34   * be replaced with 'j' (or anything else), and the number format for both real
35   * and imaginary parts can be configured.
36   *
37   */
38  public class ComplexFormat {
39  
40       /** The default imaginary character. */
41      private static final String DEFAULT_IMAGINARY_CHARACTER = "i";
42      /** The notation used to signify the imaginary part of the complex number. */
43      private final String imaginaryCharacter;
44      /** The format used for the imaginary part. */
45      private final NumberFormat imaginaryFormat;
46      /** The format used for the real part. */
47      private final NumberFormat realFormat;
48  
49      /**
50       * Create an instance with the default imaginary character, 'i', and the
51       * default number format for both real and imaginary parts.
52       */
53      public ComplexFormat() {
54          this.imaginaryCharacter = DEFAULT_IMAGINARY_CHARACTER;
55          this.imaginaryFormat = CompositeFormat.getDefaultNumberFormat();
56          this.realFormat = imaginaryFormat;
57      }
58  
59      /**
60       * Create an instance with a custom number format for both real and
61       * imaginary parts.
62       * @param format the custom format for both real and imaginary parts.
63       * @throws NullArgumentException if {@code realFormat} is {@code null}.
64       */
65      public ComplexFormat(NumberFormat format) throws NullArgumentException {
66          if (format == null) {
67              throw new NullArgumentException(LocalizedFormats.IMAGINARY_FORMAT);
68          }
69          this.imaginaryCharacter = DEFAULT_IMAGINARY_CHARACTER;
70          this.imaginaryFormat = format;
71          this.realFormat = format;
72      }
73  
74      /**
75       * Create an instance with a custom number format for the real part and a
76       * custom number format for the imaginary part.
77       * @param realFormat the custom format for the real part.
78       * @param imaginaryFormat the custom format for the imaginary part.
79       * @throws NullArgumentException if {@code imaginaryFormat} is {@code null}.
80       * @throws NullArgumentException if {@code realFormat} is {@code null}.
81        */
82      public ComplexFormat(NumberFormat realFormat, NumberFormat imaginaryFormat)
83          throws NullArgumentException {
84          if (imaginaryFormat == null) {
85              throw new NullArgumentException(LocalizedFormats.IMAGINARY_FORMAT);
86          }
87          if (realFormat == null) {
88              throw new NullArgumentException(LocalizedFormats.REAL_FORMAT);
89          }
90  
91          this.imaginaryCharacter = DEFAULT_IMAGINARY_CHARACTER;
92          this.imaginaryFormat = imaginaryFormat;
93          this.realFormat = realFormat;
94      }
95  
96      /**
97       * Create an instance with a custom imaginary character, and the default
98       * number format for both real and imaginary parts.
99       * @param imaginaryCharacter The custom imaginary character.
100      * @throws NullArgumentException if {@code imaginaryCharacter} is
101      * {@code null}.
102      * @throws NoDataException if {@code imaginaryCharacter} is an
103      * empty string.
104      */
105     public ComplexFormat(String imaginaryCharacter)
106         throws NullArgumentException, NoDataException {
107         this(imaginaryCharacter, CompositeFormat.getDefaultNumberFormat());
108     }
109 
110     /**
111      * Create an instance with a custom imaginary character, and a custom number
112      * format for both real and imaginary parts.
113      * @param imaginaryCharacter The custom imaginary character.
114      * @param format the custom format for both real and imaginary parts.
115      * @throws NullArgumentException if {@code imaginaryCharacter} is
116      * {@code null}.
117      * @throws NoDataException if {@code imaginaryCharacter} is an
118      * empty string.
119      * @throws NullArgumentException if {@code format} is {@code null}.
120      */
121     public ComplexFormat(String imaginaryCharacter, NumberFormat format)
122         throws NullArgumentException, NoDataException {
123         this(imaginaryCharacter, format, format);
124     }
125 
126     /**
127      * Create an instance with a custom imaginary character, a custom number
128      * format for the real part, and a custom number format for the imaginary
129      * part.
130      *
131      * @param imaginaryCharacter The custom imaginary character.
132      * @param realFormat the custom format for the real part.
133      * @param imaginaryFormat the custom format for the imaginary part.
134      * @throws NullArgumentException if {@code imaginaryCharacter} is
135      * {@code null}.
136      * @throws NoDataException if {@code imaginaryCharacter} is an
137      * empty string.
138      * @throws NullArgumentException if {@code imaginaryFormat} is {@code null}.
139      * @throws NullArgumentException if {@code realFormat} is {@code null}.
140      */
141     public ComplexFormat(String imaginaryCharacter,
142                          NumberFormat realFormat,
143                          NumberFormat imaginaryFormat)
144         throws NullArgumentException, NoDataException {
145         if (imaginaryCharacter == null) {
146             throw new NullArgumentException();
147         }
148         if (imaginaryCharacter.isEmpty()) {
149             throw new NoDataException();
150         }
151         if (imaginaryFormat == null) {
152             throw new NullArgumentException(LocalizedFormats.IMAGINARY_FORMAT);
153         }
154         if (realFormat == null) {
155             throw new NullArgumentException(LocalizedFormats.REAL_FORMAT);
156         }
157 
158         this.imaginaryCharacter = imaginaryCharacter;
159         this.imaginaryFormat = imaginaryFormat;
160         this.realFormat = realFormat;
161     }
162 
163     /**
164      * Get the set of locales for which complex formats are available.
165      * <p>This is the same set as the {@link NumberFormat} set.</p>
166      * @return available complex format locales.
167      */
168     public static Locale[] getAvailableLocales() {
169         return NumberFormat.getAvailableLocales();
170     }
171 
172     /**
173      * This method calls {@link #format(Object,StringBuffer,FieldPosition)}.
174      *
175      * @param c Complex object to format.
176      * @return A formatted number in the form "Re(c) + Im(c)i".
177      */
178     public String format(Complex c) {
179         return format(c, new StringBuffer(), new FieldPosition(0)).toString();
180     }
181 
182     /**
183      * This method calls {@link #format(Object,StringBuffer,FieldPosition)}.
184      *
185      * @param c Double object to format.
186      * @return A formatted number.
187      */
188     public String format(Double c) {
189         return format(Complex.ofCartesian(c, 0), new StringBuffer(), new FieldPosition(0)).toString();
190     }
191 
192     /**
193      * Formats a {@link Complex} object to produce a string.
194      *
195      * @param complex the object to format.
196      * @param toAppendTo where the text is to be appended
197      * @param pos On input: an alignment field, if desired. On output: the
198      *            offsets of the alignment field
199      * @return the value passed in as toAppendTo.
200      */
201     public StringBuffer format(Complex complex, StringBuffer toAppendTo,
202                                FieldPosition pos) {
203         pos.setBeginIndex(0);
204         pos.setEndIndex(0);
205 
206         // format real
207         double re = complex.getReal();
208         CompositeFormat.formatDouble(re, getRealFormat(), toAppendTo, pos);
209 
210         // format sign and imaginary
211         double im = complex.getImaginary();
212         StringBuffer imAppendTo;
213         if (im < 0.0) {
214             toAppendTo.append(" - ");
215             imAppendTo = formatImaginary(-im, new StringBuffer(), pos);
216             toAppendTo.append(imAppendTo);
217             toAppendTo.append(getImaginaryCharacter());
218         } else if (im > 0.0 || Double.isNaN(im)) {
219             toAppendTo.append(" + ");
220             imAppendTo = formatImaginary(im, new StringBuffer(), pos);
221             toAppendTo.append(imAppendTo);
222             toAppendTo.append(getImaginaryCharacter());
223         }
224 
225         return toAppendTo;
226     }
227 
228     /**
229      * Format the absolute value of the imaginary part.
230      *
231      * @param absIm Absolute value of the imaginary part of a complex number.
232      * @param toAppendTo where the text is to be appended.
233      * @param pos On input: an alignment field, if desired. On output: the
234      * offsets of the alignment field.
235      * @return the value passed in as toAppendTo.
236      */
237     private StringBuffer formatImaginary(double absIm,
238                                          StringBuffer toAppendTo,
239                                          FieldPosition pos) {
240         pos.setBeginIndex(0);
241         pos.setEndIndex(0);
242 
243         CompositeFormat.formatDouble(absIm, getImaginaryFormat(), toAppendTo, pos);
244         if (toAppendTo.toString().equals("1")) {
245             // Remove the character "1" if it is the only one.
246             toAppendTo.setLength(0);
247         }
248 
249         return toAppendTo;
250     }
251 
252     /**
253      * Formats a object to produce a string.  {@code obj} must be either a
254      * {@link Complex} object or a {@link Number} object.  Any other type of
255      * object will result in an {@link IllegalArgumentException} being thrown.
256      *
257      * @param obj the object to format.
258      * @param toAppendTo where the text is to be appended
259      * @param pos On input: an alignment field, if desired. On output: the
260      *            offsets of the alignment field
261      * @return the value passed in as toAppendTo.
262      * @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition)
263      * @throws MathIllegalArgumentException is {@code obj} is not a valid type.
264      */
265     public StringBuffer format(Object obj, StringBuffer toAppendTo,
266                                FieldPosition pos)
267         throws MathIllegalArgumentException {
268 
269         StringBuffer ret = null;
270 
271         if (obj instanceof Complex) {
272             ret = format( (Complex)obj, toAppendTo, pos);
273         } else if (obj instanceof Number) {
274             ret = format(Complex.ofCartesian(((Number)obj).doubleValue(), 0.0),
275                          toAppendTo, pos);
276         } else {
277             throw new MathIllegalArgumentException(LocalizedFormats.CANNOT_FORMAT_INSTANCE_AS_COMPLEX,
278                                                    obj.getClass().getName());
279         }
280 
281         return ret;
282     }
283 
284     /**
285      * Access the imaginaryCharacter.
286      * @return the imaginaryCharacter.
287      */
288     public String getImaginaryCharacter() {
289         return imaginaryCharacter;
290     }
291 
292     /**
293      * Access the imaginaryFormat.
294      * @return the imaginaryFormat.
295      */
296     public NumberFormat getImaginaryFormat() {
297         return imaginaryFormat;
298     }
299 
300     /**
301      * Returns the default complex format for the current locale.
302      * @return the default complex format.
303      */
304     public static ComplexFormat getInstance() {
305         return getInstance(Locale.getDefault());
306     }
307 
308     /**
309      * Returns the default complex format for the given locale.
310      * @param locale the specific locale used by the format.
311      * @return the complex format specific to the given locale.
312      */
313     public static ComplexFormat getInstance(Locale locale) {
314         NumberFormat f = CompositeFormat.getDefaultNumberFormat(locale);
315         return new ComplexFormat(f);
316     }
317 
318     /**
319      * Returns the default complex format for the given locale.
320      * @param locale the specific locale used by the format.
321      * @param imaginaryCharacter Imaginary character.
322      * @return the complex format specific to the given locale.
323      * @throws NullArgumentException if {@code imaginaryCharacter} is
324      * {@code null}.
325      * @throws NoDataException if {@code imaginaryCharacter} is an
326      * empty string.
327      */
328     public static ComplexFormat getInstance(String imaginaryCharacter, Locale locale)
329         throws NullArgumentException, NoDataException {
330         NumberFormat f = CompositeFormat.getDefaultNumberFormat(locale);
331         return new ComplexFormat(imaginaryCharacter, f);
332     }
333 
334     /**
335      * Access the realFormat.
336      * @return the realFormat.
337      */
338     public NumberFormat getRealFormat() {
339         return realFormat;
340     }
341 
342     /**
343      * Parses a string to produce a {@link Complex} object.
344      *
345      * @param source the string to parse.
346      * @return the parsed {@link Complex} object.
347      * @throws MathParseException if the beginning of the specified string
348      * cannot be parsed.
349      */
350     public Complex parse(String source) throws MathParseException {
351         ParsePosition parsePosition = new ParsePosition(0);
352         Complex result = parse(source, parsePosition);
353         if (parsePosition.getIndex() == 0) {
354             throw new MathParseException(source,
355                                          parsePosition.getErrorIndex(),
356                                          Complex.class);
357         }
358         return result;
359     }
360 
361     /**
362      * Parses a string to produce a {@link Complex} object.
363      *
364      * @param source the string to parse
365      * @param pos input/output parsing parameter.
366      * @return the parsed {@link Complex} object.
367      */
368     public Complex parse(String source, ParsePosition pos) {
369         int initialIndex = pos.getIndex();
370 
371         // parse whitespace
372         CompositeFormat.parseAndIgnoreWhitespace(source, pos);
373 
374         // parse real
375         Number re = CompositeFormat.parseNumber(source, getRealFormat(), pos);
376         if (re == null) {
377             // invalid real number
378             // set index back to initial, error index should already be set
379             pos.setIndex(initialIndex);
380             return null;
381         }
382 
383         // parse sign
384         int startIndex = pos.getIndex();
385         char c = CompositeFormat.parseNextCharacter(source, pos);
386         int sign = 0;
387         switch (c) {
388         case 0 :
389             // no sign
390             // return real only complex number
391             return Complex.ofCartesian(re.doubleValue(), 0.0);
392         case '-' :
393             sign = -1;
394             break;
395         case '+' :
396             sign = 1;
397             break;
398         default :
399             // invalid sign
400             // set index back to initial, error index should be the last
401             // character examined.
402             pos.setIndex(initialIndex);
403             pos.setErrorIndex(startIndex);
404             return null;
405         }
406 
407         // parse whitespace
408         CompositeFormat.parseAndIgnoreWhitespace(source, pos);
409 
410         // parse imaginary
411         Number im = CompositeFormat.parseNumber(source, getRealFormat(), pos);
412         if (im == null) {
413             // invalid imaginary number
414             // set index back to initial, error index should already be set
415             pos.setIndex(initialIndex);
416             return null;
417         }
418 
419         // parse imaginary character
420         if (!CompositeFormat.parseFixedstring(source, getImaginaryCharacter(), pos)) {
421             return null;
422         }
423 
424         return Complex.ofCartesian(re.doubleValue(), im.doubleValue() * sign);
425     }
426 }