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    package org.apache.commons.math.util;
018    
019    import java.text.FieldPosition;
020    import java.text.Format;
021    import java.text.NumberFormat;
022    import java.text.ParsePosition;
023    import java.util.Locale;
024    
025    /**
026     * Base class for formatters of composite objects (complex numbers, vectors ...).
027     *
028     * @author Apache Software Foundation
029     * @version $Revision: 705231 $ $Date: 2008-10-16 08:49:13 -0400 (Thu, 16 Oct 2008) $
030     */
031    public abstract class CompositeFormat extends Format {
032    
033        /** Serializable version identifier. */
034        private static final long serialVersionUID = 5358685519349262494L;
035    
036        /**
037         * Create a default number format.  The default number format is based on
038         * {@link NumberFormat#getInstance()} with the only customizing that the
039         * maximum number of fraction digits is set to 2.  
040         * @return the default number format.
041         */
042        protected static NumberFormat getDefaultNumberFormat() {
043            return getDefaultNumberFormat(Locale.getDefault());
044        }
045    
046        /**
047         * Create a default number format.  The default number format is based on
048         * {@link NumberFormat#getInstance(java.util.Locale)} with the only
049         * customizing that the maximum number of fraction digits is set to 2.  
050         * @param locale the specific locale used by the format.
051         * @return the default number format specific to the given locale.
052         */
053        protected static NumberFormat getDefaultNumberFormat(final Locale locale) {
054            final NumberFormat nf = NumberFormat.getInstance(locale);
055            nf.setMaximumFractionDigits(2);
056            return nf;
057        }
058    
059        /**
060         * Parses <code>source</code> until a non-whitespace character is found.
061         *
062         * @param source the string to parse
063         * @param pos input/ouput parsing parameter.  On output, <code>pos</code>
064         *        holds the index of the next non-whitespace character.
065         */
066        protected void parseAndIgnoreWhitespace(final String source,
067                                                final ParsePosition pos) {
068            parseNextCharacter(source, pos);
069            pos.setIndex(pos.getIndex() - 1);
070        }
071    
072        /**
073         * Parses <code>source</code> until a non-whitespace character is found.
074         *
075         * @param source the string to parse
076         * @param pos input/ouput parsing parameter.
077         * @return the first non-whitespace character.
078         */
079        protected char parseNextCharacter(final String source,
080                                          final ParsePosition pos) {
081             int index = pos.getIndex();
082             final int n = source.length();
083             char ret = 0;
084        
085             if (index < n) {
086                 char c;
087                 do {
088                     c = source.charAt(index++);
089                 } while (Character.isWhitespace(c) && index < n);
090                 pos.setIndex(index);
091             
092                 if (index < n) {
093                     ret = c;
094                 }
095             }
096             
097             return ret;
098        }
099    
100        /**
101         * Parses <code>source</code> for special double values.  These values
102         * include Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY.
103         *
104         * @param source the string to parse
105         * @param value the special value to parse.
106         * @param pos input/ouput parsing parameter.
107         * @return the special number.
108         */
109        private Number parseNumber(final String source, final double value,
110                                   final ParsePosition pos) {
111            Number ret = null;
112            
113            StringBuffer sb = new StringBuffer();
114            sb.append('(');
115            sb.append(value);
116            sb.append(')');
117            
118            final int n = sb.length();
119            final int startIndex = pos.getIndex();
120            final int endIndex = startIndex + n;
121            if (endIndex < source.length()) {
122                if (source.substring(startIndex, endIndex).compareTo(sb.toString()) == 0) {
123                    ret = Double.valueOf(value);
124                    pos.setIndex(endIndex);
125                }
126            }
127            
128            return ret;
129        }
130    
131        /**
132         * Parses <code>source</code> for a number.  This method can parse normal,
133         * numeric values as well as special values.  These special values include
134         * Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY.
135         *
136         * @param source the string to parse
137         * @param format the number format used to parse normal, numeric values.
138         * @param pos input/ouput parsing parameter.
139         * @return the parsed number.
140         */
141        protected Number parseNumber(final String source, final NumberFormat format,
142                                     final ParsePosition pos) {
143            final int startIndex = pos.getIndex();
144            Number number = format.parse(source, pos);
145            final int endIndex = pos.getIndex();
146            
147            // check for error parsing number
148            if (startIndex == endIndex) {
149                // try parsing special numbers
150                final double[] special = {
151                    Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY
152                };
153                for (int i = 0; i < special.length; ++i) {
154                    number = parseNumber(source, special[i], pos);
155                    if (number != null) {
156                        break;
157                    }
158                }
159            }
160            
161            return number;
162        }
163    
164        /**
165         * Parse <code>source</code> for an expected fixed string.
166         * @param source the string to parse
167         * @param expected expected string
168         * @param pos input/ouput parsing parameter.
169         * @return true if the expected string was there
170         */
171        protected boolean parseFixedstring(final String source, final String expected,
172                                           final ParsePosition pos) {
173    
174            final int startIndex = pos.getIndex();
175            final int endIndex = startIndex + expected.length();
176            if ((startIndex >= source.length()) ||
177                (endIndex > source.length()) ||
178                (source.substring(startIndex, endIndex).compareTo(expected) != 0)) {
179                // set index back to start, error index should be the start index
180                pos.setIndex(startIndex);
181                pos.setErrorIndex(startIndex);
182                return false;
183            }
184    
185            // the string was here
186            pos.setIndex(endIndex);
187            return true;
188    
189        }
190    
191        /**
192         * Formats a double value to produce a string.  In general, the value is
193         * formatted using the formatting rules of <code>format</code>.  There are
194         * three exceptions to this:
195         * <ol>
196         * <li>NaN is formatted as '(NaN)'</li>
197         * <li>Positive infinity is formatted as '(Infinity)'</li>
198         * <li>Negative infinity is formatted as '(-Infinity)'</li>
199         * </ol>
200         *
201         * @param value the double to format.
202         * @param format the format used.
203         * @param toAppendTo where the text is to be appended
204         * @param pos On input: an alignment field, if desired. On output: the
205         *            offsets of the alignment field
206         * @return the value passed in as toAppendTo.
207         */
208        protected StringBuffer formatDouble(final double value, final NumberFormat format,
209                                            final StringBuffer toAppendTo,
210                                            final FieldPosition pos) {
211            if( Double.isNaN(value) || Double.isInfinite(value) ) {
212                toAppendTo.append('(');
213                toAppendTo.append(value);
214                toAppendTo.append(')');
215            } else {
216                format.format(value, toAppendTo, pos);
217            }
218            return toAppendTo;
219        }
220    
221    }