001: /*
002: * Copyright 2004 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.apache.commons.math.complex;
018:
019: import java.io.Serializable;
020: import java.text.FieldPosition;
021: import java.text.Format;
022: import java.text.NumberFormat;
023: import java.text.ParseException;
024: import java.text.ParsePosition;
025: import java.util.Locale;
026:
027: /**
028: * Formats a Complex number in cartesian format "Re(c) + Im(c)i". 'i' can
029: * be replaced with 'j', and the number format for both real and imaginary parts
030: * can be configured.
031: *
032: * @author Apache Software Foundation
033: * @version $Revision: 348519 $ $Date: 2005-11-23 12:12:18 -0700 (Wed, 23 Nov 2005) $
034: */
035: public class ComplexFormat extends Format implements Serializable {
036:
037: /** Serializable version identifier */
038: private static final long serialVersionUID = -6337346779577272306L;
039:
040: /** The default imaginary character. */
041: private static final String DEFAULT_IMAGINARY_CHARACTER = "i";
042:
043: /** The notation used to signify the imaginary part of the complex number. */
044: private String imaginaryCharacter;
045:
046: /** The format used for the imaginary part. */
047: private NumberFormat imaginaryFormat;
048:
049: /** The format used for the real part. */
050: private NumberFormat realFormat;
051:
052: /**
053: * Create an instance with the default imaginary character, 'i', and the
054: * default number format for both real and imaginary parts.
055: */
056: public ComplexFormat() {
057: this (DEFAULT_IMAGINARY_CHARACTER, getDefaultNumberFormat());
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: */
065: public ComplexFormat(NumberFormat format) {
066: this (DEFAULT_IMAGINARY_CHARACTER, format);
067: }
068:
069: /**
070: * Create an instance with a custom number format for the real part and a
071: * custom number format for the imaginary part.
072: * @param realFormat the custom format for the real part.
073: * @param imaginaryFormat the custom format for the imaginary part.
074: */
075: public ComplexFormat(NumberFormat realFormat,
076: NumberFormat imaginaryFormat) {
077: this (DEFAULT_IMAGINARY_CHARACTER, realFormat, imaginaryFormat);
078: }
079:
080: /**
081: * Create an instance with a custom imaginary character, and the default
082: * number format for both real and imaginary parts.
083: * @param imaginaryCharacter The custom imaginary character.
084: */
085: public ComplexFormat(String imaginaryCharacter) {
086: this (imaginaryCharacter, getDefaultNumberFormat());
087: }
088:
089: /**
090: * Create an instance with a custom imaginary character, and a custom number
091: * format for both real and imaginary parts.
092: * @param imaginaryCharacter The custom imaginary character.
093: * @param format the custom format for both real and imaginary parts.
094: */
095: public ComplexFormat(String imaginaryCharacter, NumberFormat format) {
096: this (imaginaryCharacter, format, (NumberFormat) format.clone());
097: }
098:
099: /**
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,
108: NumberFormat realFormat, NumberFormat imaginaryFormat) {
109: super ();
110: setImaginaryCharacter(imaginaryCharacter);
111: setImaginaryFormat(imaginaryFormat);
112: setRealFormat(realFormat);
113: }
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: 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,
136: StringBuffer toAppendTo, FieldPosition pos) {
137:
138: pos.setBeginIndex(0);
139: pos.setEndIndex(0);
140:
141: // format real
142: double re = complex.getReal();
143: formatDouble(re, getRealFormat(), toAppendTo, pos);
144:
145: // format sign and imaginary
146: double im = complex.getImaginary();
147: if (im < 0.0) {
148: toAppendTo.append(" - ");
149: formatDouble(-im, getImaginaryFormat(), toAppendTo, pos);
150: toAppendTo.append(getImaginaryCharacter());
151: } else if (im > 0.0 || Double.isNaN(im)) {
152: toAppendTo.append(" + ");
153: formatDouble(im, getImaginaryFormat(), toAppendTo, pos);
154: toAppendTo.append(getImaginaryCharacter());
155: }
156:
157: 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: StringBuffer ret = null;
177:
178: if (obj instanceof Complex) {
179: ret = format((Complex) obj, toAppendTo, pos);
180: } else if (obj instanceof Number) {
181: ret = format(
182: new Complex(((Number) obj).doubleValue(), 0.0),
183: toAppendTo, pos);
184: } else {
185: throw new IllegalArgumentException(
186: "Cannot format given Object as a Date");
187: }
188:
189: return ret;
190: }
191:
192: /**
193: * Formats a double value to produce a string. In general, the value is
194: * formatted using the formatting rules of <code>format</code>. There are
195: * three exceptions to this:
196: * <ol>
197: * <li>NaN is formatted as '(NaN)'</li>
198: * <li>Positive infinity is formatted as '(Infinity)'</li>
199: * <li>Negative infinity is formatted as '(-Infinity)'</li>
200: * </ol>
201: *
202: * @param value the double to format.
203: * @param format the format used.
204: * @param toAppendTo where the text is to be appended
205: * @param pos On input: an alignment field, if desired. On output: the
206: * offsets of the alignment field
207: * @return the value passed in as toAppendTo.
208: */
209: private StringBuffer formatDouble(double value,
210: NumberFormat format, StringBuffer toAppendTo,
211: FieldPosition pos) {
212: if (Double.isNaN(value) || Double.isInfinite(value)) {
213: toAppendTo.append('(');
214: toAppendTo.append(value);
215: toAppendTo.append(')');
216: } else {
217: getRealFormat().format(value, toAppendTo, pos);
218: }
219: return toAppendTo;
220: }
221:
222: /**
223: * Get the set of locales for which complex formats are available. This
224: * is the same set as the {@link NumberFormat} set.
225: * @return available complex format locales.
226: */
227: public static Locale[] getAvailableLocales() {
228: return NumberFormat.getAvailableLocales();
229: }
230:
231: /**
232: * Create a default number format. The default number format is based on
233: * {@link NumberFormat#getInstance()} with the only customizing is the
234: * maximum number of fraction digits, which is set to 2.
235: * @return the default number format.
236: */
237: private static NumberFormat getDefaultNumberFormat() {
238: return getDefaultNumberFormat(Locale.getDefault());
239: }
240:
241: /**
242: * Create a default number format. The default number format is based on
243: * {@link NumberFormat#getInstance(java.util.Locale)} with the only
244: * customizing is the maximum number of fraction digits, which is set to 2.
245: * @param locale the specific locale used by the format.
246: * @return the default number format specific to the given locale.
247: */
248: private static NumberFormat getDefaultNumberFormat(Locale locale) {
249: NumberFormat nf = NumberFormat.getInstance(locale);
250: nf.setMaximumFractionDigits(2);
251: return nf;
252: }
253:
254: /**
255: * Access the imaginaryCharacter.
256: * @return the imaginaryCharacter.
257: */
258: public String getImaginaryCharacter() {
259: return imaginaryCharacter;
260: }
261:
262: /**
263: * Access the imaginaryFormat.
264: * @return the imaginaryFormat.
265: */
266: public NumberFormat getImaginaryFormat() {
267: return imaginaryFormat;
268: }
269:
270: /**
271: * Returns the default complex format for the current locale.
272: * @return the default complex format.
273: */
274: public static ComplexFormat getInstance() {
275: return getInstance(Locale.getDefault());
276: }
277:
278: /**
279: * Returns the default complex format for the given locale.
280: * @param locale the specific locale used by the format.
281: * @return the complex format specific to the given locale.
282: */
283: public static ComplexFormat getInstance(Locale locale) {
284: NumberFormat f = getDefaultNumberFormat(locale);
285: return new ComplexFormat(f);
286: }
287:
288: /**
289: * Access the realFormat.
290: * @return the realFormat.
291: */
292: public NumberFormat getRealFormat() {
293: return realFormat;
294: }
295:
296: /**
297: * Parses a string to produce a {@link Complex} object.
298: *
299: * @param source the string to parse
300: * @return the parsed {@link Complex} object.
301: * @exception ParseException if the beginning of the specified string
302: * cannot be parsed.
303: */
304: public Complex parse(String source) throws ParseException {
305: ParsePosition parsePosition = new ParsePosition(0);
306: Complex result = parse(source, parsePosition);
307: if (parsePosition.getIndex() == 0) {
308: throw new ParseException("Unparseable complex number: \""
309: + source + "\"", parsePosition.getErrorIndex());
310: }
311: return result;
312: }
313:
314: /**
315: * Parses a string to produce a {@link Complex} object.
316: *
317: * @param source the string to parse
318: * @param pos input/ouput parsing parameter.
319: * @return the parsed {@link Complex} object.
320: */
321: public Complex parse(String source, ParsePosition pos) {
322: int initialIndex = pos.getIndex();
323:
324: // parse whitespace
325: parseAndIgnoreWhitespace(source, pos);
326:
327: // parse real
328: Number re = parseNumber(source, getRealFormat(), pos);
329: if (re == null) {
330: // invalid real number
331: // set index back to initial, error index should already be set
332: // character examined.
333: pos.setIndex(initialIndex);
334: return null;
335: }
336:
337: // parse sign
338: int startIndex = pos.getIndex();
339: char c = parseNextCharacter(source, pos);
340: int sign = 0;
341: switch (c) {
342: case 0:
343: // no sign
344: // return real only complex number
345: return new Complex(re.doubleValue(), 0.0);
346: case '-':
347: sign = -1;
348: break;
349: case '+':
350: sign = 1;
351: break;
352: default:
353: // invalid sign
354: // set index back to initial, error index should be the last
355: // character examined.
356: pos.setIndex(initialIndex);
357: pos.setErrorIndex(startIndex);
358: return null;
359: }
360:
361: // parse whitespace
362: parseAndIgnoreWhitespace(source, pos);
363:
364: // parse imaginary
365: Number im = parseNumber(source, getRealFormat(), pos);
366: if (im == null) {
367: // invalid imaginary number
368: // set index back to initial, error index should already be set
369: // character examined.
370: pos.setIndex(initialIndex);
371: return null;
372: }
373:
374: // parse imaginary character
375: int n = getImaginaryCharacter().length();
376: startIndex = pos.getIndex();
377: int endIndex = startIndex + n;
378: if (source.substring(startIndex, endIndex).compareTo(
379: getImaginaryCharacter()) != 0) {
380: // set index back to initial, error index should be the start index
381: // character examined.
382: pos.setIndex(initialIndex);
383: pos.setErrorIndex(startIndex);
384: return null;
385: }
386: pos.setIndex(endIndex);
387:
388: return new Complex(re.doubleValue(), im.doubleValue() * sign);
389: }
390:
391: /**
392: * Parses <code>source</code> until a non-whitespace character is found.
393: *
394: * @param source the string to parse
395: * @param pos input/ouput parsing parameter. On output, <code>pos</code>
396: * holds the index of the next non-whitespace character.
397: */
398: private void parseAndIgnoreWhitespace(String source,
399: ParsePosition pos) {
400: parseNextCharacter(source, pos);
401: pos.setIndex(pos.getIndex() - 1);
402: }
403:
404: /**
405: * Parses <code>source</code> until a non-whitespace character is found.
406: *
407: * @param source the string to parse
408: * @param pos input/ouput parsing parameter.
409: * @return the first non-whitespace character.
410: */
411: private char parseNextCharacter(String source, ParsePosition pos) {
412: int index = pos.getIndex();
413: int n = source.length();
414: char ret = 0;
415:
416: if (index < n) {
417: char c;
418: do {
419: c = source.charAt(index++);
420: } while (Character.isWhitespace(c) && index < n);
421: pos.setIndex(index);
422:
423: if (index < n) {
424: ret = c;
425: }
426: }
427:
428: return ret;
429: }
430:
431: /**
432: * Parses <code>source</code> for a special double values. These values
433: * include Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY.
434: *
435: * @param source the string to parse
436: * @param value the special value to parse.
437: * @param pos input/ouput parsing parameter.
438: * @return the special number.
439: */
440: private Number parseNumber(String source, double value,
441: ParsePosition pos) {
442: Number ret = null;
443:
444: StringBuffer sb = new StringBuffer();
445: sb.append('(');
446: sb.append(value);
447: sb.append(')');
448:
449: int n = sb.length();
450: int startIndex = pos.getIndex();
451: int endIndex = startIndex + n;
452: if (endIndex < source.length()) {
453: if (source.substring(startIndex, endIndex).compareTo(
454: sb.toString()) == 0) {
455: ret = new Double(value);
456: pos.setIndex(endIndex);
457: }
458: }
459:
460: return ret;
461: }
462:
463: /**
464: * Parses <code>source</code> for a number. This method can parse normal,
465: * numeric values as well as special values. These special values include
466: * Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY.
467: *
468: * @param source the string to parse
469: * @param format the number format used to parse normal, numeric values.
470: * @param pos input/ouput parsing parameter.
471: * @return the parsed number.
472: */
473: private Number parseNumber(String source, NumberFormat format,
474: ParsePosition pos) {
475: int startIndex = pos.getIndex();
476: Number number = getRealFormat().parse(source, pos);
477: int endIndex = pos.getIndex();
478:
479: // check for error parsing number
480: if (startIndex == endIndex) {
481: // try parsing special numbers
482: double[] special = { Double.NaN, Double.POSITIVE_INFINITY,
483: Double.NEGATIVE_INFINITY };
484: for (int i = 0; i < special.length; ++i) {
485: number = parseNumber(source, special[i], pos);
486: if (number != null) {
487: break;
488: }
489: }
490: }
491:
492: return number;
493: }
494:
495: /**
496: * Parses a string to produce a object.
497: *
498: * @param source the string to parse
499: * @param pos input/ouput parsing parameter.
500: * @return the parsed object.
501: * @see java.text.Format#parseObject(java.lang.String, java.text.ParsePosition)
502: */
503: public Object parseObject(String source, ParsePosition pos) {
504: return parse(source, pos);
505: }
506:
507: /**
508: * Modify the imaginaryCharacter.
509: * @param imaginaryCharacter The new imaginaryCharacter value.
510: * @throws IllegalArgumentException if <code>imaginaryCharacter</code> is
511: * <code>null</code> or an empty string.
512: */
513: public void setImaginaryCharacter(String imaginaryCharacter) {
514: if (imaginaryCharacter == null
515: || imaginaryCharacter.length() == 0) {
516: throw new IllegalArgumentException(
517: "imaginaryCharacter must be a non-empty string.");
518: }
519: this .imaginaryCharacter = imaginaryCharacter;
520: }
521:
522: /**
523: * Modify the imaginaryFormat.
524: * @param imaginaryFormat The new imaginaryFormat value.
525: * @throws IllegalArgumentException if <code>imaginaryFormat</code> is
526: * <code>null</code>.
527: */
528: public void setImaginaryFormat(NumberFormat imaginaryFormat) {
529: if (imaginaryFormat == null) {
530: throw new IllegalArgumentException(
531: "imaginaryFormat can not be null.");
532: }
533: this .imaginaryFormat = imaginaryFormat;
534: }
535:
536: /**
537: * Modify the realFormat.
538: * @param realFormat The new realFormat value.
539: * @throws IllegalArgumentException if <code>realFormat</code> is
540: * <code>null</code>.
541: */
542: public void setRealFormat(NumberFormat realFormat) {
543: if (realFormat == null) {
544: throw new IllegalArgumentException(
545: "realFormat can not be null.");
546: }
547: this.realFormat = realFormat;
548: }
549: }
|