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.math.util; 18 19 import java.text.FieldPosition; 20 import java.text.Format; 21 import java.text.NumberFormat; 22 import java.text.ParsePosition; 23 import java.util.Locale; 24 25 /** 26 * Base class for formatters of composite objects (complex numbers, vectors ...). 27 * 28 * @version $Revision: 1042376 $ $Date: 2010-12-05 16:54:55 +0100 (dim. 05 dc. 2010) $ 29 */ 30 public abstract class CompositeFormat extends Format { 31 32 /** Serializable version identifier. */ 33 private static final long serialVersionUID = 5358685519349262494L; 34 35 /** 36 * Create a default number format. The default number format is based on 37 * {@link NumberFormat#getInstance()} with the only customizing that the 38 * maximum number of fraction digits is set to 2. 39 * @return the default number format. 40 */ 41 protected static NumberFormat getDefaultNumberFormat() { 42 return getDefaultNumberFormat(Locale.getDefault()); 43 } 44 45 /** 46 * Create a default number format. The default number format is based on 47 * {@link NumberFormat#getInstance(java.util.Locale)} with the only 48 * customizing that the maximum number of fraction digits is set to 2. 49 * @param locale the specific locale used by the format. 50 * @return the default number format specific to the given locale. 51 */ 52 protected static NumberFormat getDefaultNumberFormat(final Locale locale) { 53 final NumberFormat nf = NumberFormat.getInstance(locale); 54 nf.setMaximumFractionDigits(2); 55 return nf; 56 } 57 58 /** 59 * Parses <code>source</code> until a non-whitespace character is found. 60 * 61 * @param source the string to parse 62 * @param pos input/ouput parsing parameter. On output, <code>pos</code> 63 * holds the index of the next non-whitespace character. 64 */ 65 protected void parseAndIgnoreWhitespace(final String source, 66 final ParsePosition pos) { 67 parseNextCharacter(source, pos); 68 pos.setIndex(pos.getIndex() - 1); 69 } 70 71 /** 72 * Parses <code>source</code> until a non-whitespace character is found. 73 * 74 * @param source the string to parse 75 * @param pos input/ouput parsing parameter. 76 * @return the first non-whitespace character. 77 */ 78 protected char parseNextCharacter(final String source, 79 final ParsePosition pos) { 80 int index = pos.getIndex(); 81 final int n = source.length(); 82 char ret = 0; 83 84 if (index < n) { 85 char c; 86 do { 87 c = source.charAt(index++); 88 } while (Character.isWhitespace(c) && index < n); 89 pos.setIndex(index); 90 91 if (index < n) { 92 ret = c; 93 } 94 } 95 96 return ret; 97 } 98 99 /** 100 * Parses <code>source</code> for special double values. These values 101 * include Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY. 102 * 103 * @param source the string to parse 104 * @param value the special value to parse. 105 * @param pos input/ouput parsing parameter. 106 * @return the special number. 107 */ 108 private Number parseNumber(final String source, final double value, 109 final ParsePosition pos) { 110 Number ret = null; 111 112 StringBuilder sb = new StringBuilder(); 113 sb.append('('); 114 sb.append(value); 115 sb.append(')'); 116 117 final int n = sb.length(); 118 final int startIndex = pos.getIndex(); 119 final int endIndex = startIndex + n; 120 if (endIndex < source.length()) { 121 if (source.substring(startIndex, endIndex).compareTo(sb.toString()) == 0) { 122 ret = Double.valueOf(value); 123 pos.setIndex(endIndex); 124 } 125 } 126 127 return ret; 128 } 129 130 /** 131 * Parses <code>source</code> for a number. This method can parse normal, 132 * numeric values as well as special values. These special values include 133 * Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY. 134 * 135 * @param source the string to parse 136 * @param format the number format used to parse normal, numeric values. 137 * @param pos input/ouput parsing parameter. 138 * @return the parsed number. 139 */ 140 protected Number parseNumber(final String source, final NumberFormat format, 141 final ParsePosition pos) { 142 final int startIndex = pos.getIndex(); 143 Number number = format.parse(source, pos); 144 final int endIndex = pos.getIndex(); 145 146 // check for error parsing number 147 if (startIndex == endIndex) { 148 // try parsing special numbers 149 final double[] special = { 150 Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY 151 }; 152 for (int i = 0; i < special.length; ++i) { 153 number = parseNumber(source, special[i], pos); 154 if (number != null) { 155 break; 156 } 157 } 158 } 159 160 return number; 161 } 162 163 /** 164 * Parse <code>source</code> for an expected fixed string. 165 * @param source the string to parse 166 * @param expected expected string 167 * @param pos input/ouput parsing parameter. 168 * @return true if the expected string was there 169 */ 170 protected boolean parseFixedstring(final String source, final String expected, 171 final ParsePosition pos) { 172 173 final int startIndex = pos.getIndex(); 174 final int endIndex = startIndex + expected.length(); 175 if ((startIndex >= source.length()) || 176 (endIndex > source.length()) || 177 (source.substring(startIndex, endIndex).compareTo(expected) != 0)) { 178 // set index back to start, error index should be the start index 179 pos.setIndex(startIndex); 180 pos.setErrorIndex(startIndex); 181 return false; 182 } 183 184 // the string was here 185 pos.setIndex(endIndex); 186 return true; 187 188 } 189 190 /** 191 * Formats a double value to produce a string. In general, the value is 192 * formatted using the formatting rules of <code>format</code>. There are 193 * three exceptions to this: 194 * <ol> 195 * <li>NaN is formatted as '(NaN)'</li> 196 * <li>Positive infinity is formatted as '(Infinity)'</li> 197 * <li>Negative infinity is formatted as '(-Infinity)'</li> 198 * </ol> 199 * 200 * @param value the double to format. 201 * @param format the format used. 202 * @param toAppendTo where the text is to be appended 203 * @param pos On input: an alignment field, if desired. On output: the 204 * offsets of the alignment field 205 * @return the value passed in as toAppendTo. 206 */ 207 protected StringBuffer formatDouble(final double value, final NumberFormat format, 208 final StringBuffer toAppendTo, 209 final FieldPosition pos) { 210 if( Double.isNaN(value) || Double.isInfinite(value) ) { 211 toAppendTo.append('('); 212 toAppendTo.append(value); 213 toAppendTo.append(')'); 214 } else { 215 format.format(value, toAppendTo, pos); 216 } 217 return toAppendTo; 218 } 219 220 } 221