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 }