ComplexFormat.java

  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. package org.apache.commons.math4.legacy.util;

  18. import java.text.FieldPosition;
  19. import java.text.NumberFormat;
  20. import java.text.ParsePosition;
  21. import java.util.Locale;

  22. import org.apache.commons.numbers.complex.Complex;
  23. import org.apache.commons.math4.legacy.exception.MathIllegalArgumentException;
  24. import org.apache.commons.math4.legacy.exception.MathParseException;
  25. import org.apache.commons.math4.legacy.exception.NoDataException;
  26. import org.apache.commons.math4.legacy.exception.NullArgumentException;
  27. import org.apache.commons.math4.legacy.exception.util.LocalizedFormats;

  28. /**
  29.  * Formats a Complex number in cartesian format "Re(c) + Im(c)i".  'i' can
  30.  * be replaced with 'j' (or anything else), and the number format for both real
  31.  * and imaginary parts can be configured.
  32.  *
  33.  */
  34. public class ComplexFormat {

  35.      /** The default imaginary character. */
  36.     private static final String DEFAULT_IMAGINARY_CHARACTER = "i";
  37.     /** The notation used to signify the imaginary part of the complex number. */
  38.     private final String imaginaryCharacter;
  39.     /** The format used for the imaginary part. */
  40.     private final NumberFormat imaginaryFormat;
  41.     /** The format used for the real part. */
  42.     private final NumberFormat realFormat;

  43.     /**
  44.      * Create an instance with the default imaginary character, 'i', and the
  45.      * default number format for both real and imaginary parts.
  46.      */
  47.     public ComplexFormat() {
  48.         this.imaginaryCharacter = DEFAULT_IMAGINARY_CHARACTER;
  49.         this.imaginaryFormat = CompositeFormat.getDefaultNumberFormat();
  50.         this.realFormat = imaginaryFormat;
  51.     }

  52.     /**
  53.      * Create an instance with a custom number format for both real and
  54.      * imaginary parts.
  55.      * @param format the custom format for both real and imaginary parts.
  56.      * @throws NullArgumentException if {@code realFormat} is {@code null}.
  57.      */
  58.     public ComplexFormat(NumberFormat format) throws NullArgumentException {
  59.         if (format == null) {
  60.             throw new NullArgumentException(LocalizedFormats.IMAGINARY_FORMAT);
  61.         }
  62.         this.imaginaryCharacter = DEFAULT_IMAGINARY_CHARACTER;
  63.         this.imaginaryFormat = format;
  64.         this.realFormat = format;
  65.     }

  66.     /**
  67.      * Create an instance with a custom number format for the real part and a
  68.      * custom number format for the imaginary part.
  69.      * @param realFormat the custom format for the real part.
  70.      * @param imaginaryFormat the custom format for the imaginary part.
  71.      * @throws NullArgumentException if {@code imaginaryFormat} is {@code null}.
  72.      * @throws NullArgumentException if {@code realFormat} is {@code null}.
  73.       */
  74.     public ComplexFormat(NumberFormat realFormat, NumberFormat imaginaryFormat)
  75.         throws NullArgumentException {
  76.         if (imaginaryFormat == null) {
  77.             throw new NullArgumentException(LocalizedFormats.IMAGINARY_FORMAT);
  78.         }
  79.         if (realFormat == null) {
  80.             throw new NullArgumentException(LocalizedFormats.REAL_FORMAT);
  81.         }

  82.         this.imaginaryCharacter = DEFAULT_IMAGINARY_CHARACTER;
  83.         this.imaginaryFormat = imaginaryFormat;
  84.         this.realFormat = realFormat;
  85.     }

  86.     /**
  87.      * Create an instance with a custom imaginary character, and the default
  88.      * number format for both real and imaginary parts.
  89.      * @param imaginaryCharacter The custom imaginary character.
  90.      * @throws NullArgumentException if {@code imaginaryCharacter} is
  91.      * {@code null}.
  92.      * @throws NoDataException if {@code imaginaryCharacter} is an
  93.      * empty string.
  94.      */
  95.     public ComplexFormat(String imaginaryCharacter)
  96.         throws NullArgumentException, NoDataException {
  97.         this(imaginaryCharacter, CompositeFormat.getDefaultNumberFormat());
  98.     }

  99.     /**
  100.      * Create an instance with a custom imaginary character, and a custom number
  101.      * format for both real and imaginary parts.
  102.      * @param imaginaryCharacter The custom imaginary character.
  103.      * @param format the custom format for both real and imaginary parts.
  104.      * @throws NullArgumentException if {@code imaginaryCharacter} is
  105.      * {@code null}.
  106.      * @throws NoDataException if {@code imaginaryCharacter} is an
  107.      * empty string.
  108.      * @throws NullArgumentException if {@code format} is {@code null}.
  109.      */
  110.     public ComplexFormat(String imaginaryCharacter, NumberFormat format)
  111.         throws NullArgumentException, NoDataException {
  112.         this(imaginaryCharacter, format, format);
  113.     }

  114.     /**
  115.      * Create an instance with a custom imaginary character, a custom number
  116.      * format for the real part, and a custom number format for the imaginary
  117.      * part.
  118.      *
  119.      * @param imaginaryCharacter The custom imaginary character.
  120.      * @param realFormat the custom format for the real part.
  121.      * @param imaginaryFormat the custom format for the imaginary part.
  122.      * @throws NullArgumentException if {@code imaginaryCharacter} is
  123.      * {@code null}.
  124.      * @throws NoDataException if {@code imaginaryCharacter} is an
  125.      * empty string.
  126.      * @throws NullArgumentException if {@code imaginaryFormat} is {@code null}.
  127.      * @throws NullArgumentException if {@code realFormat} is {@code null}.
  128.      */
  129.     public ComplexFormat(String imaginaryCharacter,
  130.                          NumberFormat realFormat,
  131.                          NumberFormat imaginaryFormat)
  132.         throws NullArgumentException, NoDataException {
  133.         if (imaginaryCharacter == null) {
  134.             throw new NullArgumentException();
  135.         }
  136.         if (imaginaryCharacter.isEmpty()) {
  137.             throw new NoDataException();
  138.         }
  139.         if (imaginaryFormat == null) {
  140.             throw new NullArgumentException(LocalizedFormats.IMAGINARY_FORMAT);
  141.         }
  142.         if (realFormat == null) {
  143.             throw new NullArgumentException(LocalizedFormats.REAL_FORMAT);
  144.         }

  145.         this.imaginaryCharacter = imaginaryCharacter;
  146.         this.imaginaryFormat = imaginaryFormat;
  147.         this.realFormat = realFormat;
  148.     }

  149.     /**
  150.      * Get the set of locales for which complex formats are available.
  151.      * <p>This is the same set as the {@link NumberFormat} set.</p>
  152.      * @return available complex format locales.
  153.      */
  154.     public static Locale[] getAvailableLocales() {
  155.         return NumberFormat.getAvailableLocales();
  156.     }

  157.     /**
  158.      * This method calls {@link #format(Object,StringBuffer,FieldPosition)}.
  159.      *
  160.      * @param c Complex object to format.
  161.      * @return A formatted number in the form "Re(c) + Im(c)i".
  162.      */
  163.     public String format(Complex c) {
  164.         return format(c, new StringBuffer(), new FieldPosition(0)).toString();
  165.     }

  166.     /**
  167.      * This method calls {@link #format(Object,StringBuffer,FieldPosition)}.
  168.      *
  169.      * @param c Double object to format.
  170.      * @return A formatted number.
  171.      */
  172.     public String format(Double c) {
  173.         return format(Complex.ofCartesian(c, 0), new StringBuffer(), new FieldPosition(0)).toString();
  174.     }

  175.     /**
  176.      * Formats a {@link Complex} object to produce a string.
  177.      *
  178.      * @param complex the object to format.
  179.      * @param toAppendTo where the text is to be appended
  180.      * @param pos On input: an alignment field, if desired. On output: the
  181.      *            offsets of the alignment field
  182.      * @return the value passed in as toAppendTo.
  183.      */
  184.     public StringBuffer format(Complex complex, StringBuffer toAppendTo,
  185.                                FieldPosition pos) {
  186.         pos.setBeginIndex(0);
  187.         pos.setEndIndex(0);

  188.         // format real
  189.         double re = complex.getReal();
  190.         CompositeFormat.formatDouble(re, getRealFormat(), toAppendTo, pos);

  191.         // format sign and imaginary
  192.         double im = complex.getImaginary();
  193.         StringBuffer imAppendTo;
  194.         if (im < 0.0) {
  195.             toAppendTo.append(" - ");
  196.             imAppendTo = formatImaginary(-im, new StringBuffer(), pos);
  197.             toAppendTo.append(imAppendTo);
  198.             toAppendTo.append(getImaginaryCharacter());
  199.         } else if (im > 0.0 || Double.isNaN(im)) {
  200.             toAppendTo.append(" + ");
  201.             imAppendTo = formatImaginary(im, new StringBuffer(), pos);
  202.             toAppendTo.append(imAppendTo);
  203.             toAppendTo.append(getImaginaryCharacter());
  204.         }

  205.         return toAppendTo;
  206.     }

  207.     /**
  208.      * Format the absolute value of the imaginary part.
  209.      *
  210.      * @param absIm Absolute value of the imaginary part of a complex number.
  211.      * @param toAppendTo where the text is to be appended.
  212.      * @param pos On input: an alignment field, if desired. On output: the
  213.      * offsets of the alignment field.
  214.      * @return the value passed in as toAppendTo.
  215.      */
  216.     private StringBuffer formatImaginary(double absIm,
  217.                                          StringBuffer toAppendTo,
  218.                                          FieldPosition pos) {
  219.         pos.setBeginIndex(0);
  220.         pos.setEndIndex(0);

  221.         CompositeFormat.formatDouble(absIm, getImaginaryFormat(), toAppendTo, pos);
  222.         if (toAppendTo.toString().equals("1")) {
  223.             // Remove the character "1" if it is the only one.
  224.             toAppendTo.setLength(0);
  225.         }

  226.         return toAppendTo;
  227.     }

  228.     /**
  229.      * Formats a object to produce a string.  {@code obj} must be either a
  230.      * {@link Complex} object or a {@link Number} object.  Any other type of
  231.      * object will result in an {@link IllegalArgumentException} being thrown.
  232.      *
  233.      * @param obj the object to format.
  234.      * @param toAppendTo where the text is to be appended
  235.      * @param pos On input: an alignment field, if desired. On output: the
  236.      *            offsets of the alignment field
  237.      * @return the value passed in as toAppendTo.
  238.      * @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition)
  239.      * @throws MathIllegalArgumentException is {@code obj} is not a valid type.
  240.      */
  241.     public StringBuffer format(Object obj, StringBuffer toAppendTo,
  242.                                FieldPosition pos)
  243.         throws MathIllegalArgumentException {

  244.         StringBuffer ret = null;

  245.         if (obj instanceof Complex) {
  246.             ret = format( (Complex)obj, toAppendTo, pos);
  247.         } else if (obj instanceof Number) {
  248.             ret = format(Complex.ofCartesian(((Number)obj).doubleValue(), 0.0),
  249.                          toAppendTo, pos);
  250.         } else {
  251.             throw new MathIllegalArgumentException(LocalizedFormats.CANNOT_FORMAT_INSTANCE_AS_COMPLEX,
  252.                                                    obj.getClass().getName());
  253.         }

  254.         return ret;
  255.     }

  256.     /**
  257.      * Access the imaginaryCharacter.
  258.      * @return the imaginaryCharacter.
  259.      */
  260.     public String getImaginaryCharacter() {
  261.         return imaginaryCharacter;
  262.     }

  263.     /**
  264.      * Access the imaginaryFormat.
  265.      * @return the imaginaryFormat.
  266.      */
  267.     public NumberFormat getImaginaryFormat() {
  268.         return imaginaryFormat;
  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.      * Returns the default complex format for the given locale.
  279.      * @param locale the specific locale used by the format.
  280.      * @return the complex format specific to the given locale.
  281.      */
  282.     public static ComplexFormat getInstance(Locale locale) {
  283.         NumberFormat f = CompositeFormat.getDefaultNumberFormat(locale);
  284.         return new ComplexFormat(f);
  285.     }

  286.     /**
  287.      * Returns the default complex format for the given locale.
  288.      * @param locale the specific locale used by the format.
  289.      * @param imaginaryCharacter Imaginary character.
  290.      * @return the complex format specific to the given locale.
  291.      * @throws NullArgumentException if {@code imaginaryCharacter} is
  292.      * {@code null}.
  293.      * @throws NoDataException if {@code imaginaryCharacter} is an
  294.      * empty string.
  295.      */
  296.     public static ComplexFormat getInstance(String imaginaryCharacter, Locale locale)
  297.         throws NullArgumentException, NoDataException {
  298.         NumberFormat f = CompositeFormat.getDefaultNumberFormat(locale);
  299.         return new ComplexFormat(imaginaryCharacter, f);
  300.     }

  301.     /**
  302.      * Access the realFormat.
  303.      * @return the realFormat.
  304.      */
  305.     public NumberFormat getRealFormat() {
  306.         return realFormat;
  307.     }

  308.     /**
  309.      * Parses a string to produce a {@link Complex} object.
  310.      *
  311.      * @param source the string to parse.
  312.      * @return the parsed {@link Complex} object.
  313.      * @throws MathParseException if the beginning of the specified string
  314.      * cannot be parsed.
  315.      */
  316.     public Complex parse(String source) throws MathParseException {
  317.         ParsePosition parsePosition = new ParsePosition(0);
  318.         Complex result = parse(source, parsePosition);
  319.         if (parsePosition.getIndex() == 0) {
  320.             throw new MathParseException(source,
  321.                                          parsePosition.getErrorIndex(),
  322.                                          Complex.class);
  323.         }
  324.         return result;
  325.     }

  326.     /**
  327.      * Parses a string to produce a {@link Complex} object.
  328.      *
  329.      * @param source the string to parse
  330.      * @param pos input/output parsing parameter.
  331.      * @return the parsed {@link Complex} object.
  332.      */
  333.     public Complex parse(String source, ParsePosition pos) {
  334.         int initialIndex = pos.getIndex();

  335.         // parse whitespace
  336.         CompositeFormat.parseAndIgnoreWhitespace(source, pos);

  337.         // parse real
  338.         Number re = CompositeFormat.parseNumber(source, getRealFormat(), pos);
  339.         if (re == null) {
  340.             // invalid real number
  341.             // set index back to initial, error index should already be set
  342.             pos.setIndex(initialIndex);
  343.             return null;
  344.         }

  345.         // parse sign
  346.         int startIndex = pos.getIndex();
  347.         char c = CompositeFormat.parseNextCharacter(source, pos);
  348.         int sign = 0;
  349.         switch (c) {
  350.         case 0 :
  351.             // no sign
  352.             // return real only complex number
  353.             return Complex.ofCartesian(re.doubleValue(), 0.0);
  354.         case '-' :
  355.             sign = -1;
  356.             break;
  357.         case '+' :
  358.             sign = 1;
  359.             break;
  360.         default :
  361.             // invalid sign
  362.             // set index back to initial, error index should be the last
  363.             // character examined.
  364.             pos.setIndex(initialIndex);
  365.             pos.setErrorIndex(startIndex);
  366.             return null;
  367.         }

  368.         // parse whitespace
  369.         CompositeFormat.parseAndIgnoreWhitespace(source, pos);

  370.         // parse imaginary
  371.         Number im = CompositeFormat.parseNumber(source, getRealFormat(), pos);
  372.         if (im == null) {
  373.             // invalid imaginary number
  374.             // set index back to initial, error index should already be set
  375.             pos.setIndex(initialIndex);
  376.             return null;
  377.         }

  378.         // parse imaginary character
  379.         if (!CompositeFormat.parseFixedstring(source, getImaginaryCharacter(), pos)) {
  380.             return null;
  381.         }

  382.         return Complex.ofCartesian(re.doubleValue(), im.doubleValue() * sign);
  383.     }
  384. }