/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.math.util;
import java.text.FieldPosition;
import java.text.NumberFormat;
import java.text.ParsePosition;
import java.util.Locale;
/**
* Base class for formatters of composite objects (complex numbers, vectors ...).
*
* @version $Id: CompositeFormat.java 1131229 2011-06-03 20:49:25Z luc $
*/
public class CompositeFormat {
/** Serializable version identifier. */
private static final long serialVersionUID = 5358685519349262494L;
/**
* Class contains only static methods.
*/
private CompositeFormat() {}
/**
* Create a default number format. The default number format is based on
* {@link NumberFormat#getInstance()} with the only customizing that the
* maximum number of fraction digits is set to 2.
* @return the default number format.
*/
public static NumberFormat getDefaultNumberFormat() {
return getDefaultNumberFormat(Locale.getDefault());
}
/**
* Create a default number format. The default number format is based on
* {@link NumberFormat#getInstance(java.util.Locale)} with the only
* customizing that the maximum number of fraction digits is set to 2.
* @param locale the specific locale used by the format.
* @return the default number format specific to the given locale.
*/
public static NumberFormat getDefaultNumberFormat(final Locale locale) {
final NumberFormat nf = NumberFormat.getInstance(locale);
nf.setMaximumFractionDigits(2);
return nf;
}
/**
* Parses <code>source</code> until a non-whitespace character is found.
*
* @param source the string to parse
* @param pos input/ouput parsing parameter. On output, <code>pos</code>
* holds the index of the next non-whitespace character.
*/
public static void parseAndIgnoreWhitespace(final String source,
final ParsePosition pos) {
parseNextCharacter(source, pos);
pos.setIndex(pos.getIndex() - 1);
}
/**
* Parses <code>source</code> until a non-whitespace character is found.
*
* @param source the string to parse
* @param pos input/ouput parsing parameter.
* @return the first non-whitespace character.
*/
public static char parseNextCharacter(final String source,
final ParsePosition pos) {
int index = pos.getIndex();
final int n = source.length();
char ret = 0;
if (index < n) {
char c;
do {
c = source.charAt(index++);
} while (Character.isWhitespace(c) && index < n);
pos.setIndex(index);
if (index < n) {
ret = c;
}
}
return ret;
}
/**
* Parses <code>source</code> for special double values. These values
* include Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY.
*
* @param source the string to parse
* @param value the special value to parse.
* @param pos input/ouput parsing parameter.
* @return the special number.
*/
private static Number parseNumber(final String source, final double value,
final ParsePosition pos) {
Number ret = null;
StringBuilder sb = new StringBuilder();
sb.append('(');
sb.append(value);
sb.append(')');
final int n = sb.length();
final int startIndex = pos.getIndex();
final int endIndex = startIndex + n;
if (endIndex < source.length()) {
if (source.substring(startIndex, endIndex).compareTo(sb.toString()) == 0) {
ret = Double.valueOf(value);
pos.setIndex(endIndex);
}
}
return ret;
}
/**
* Parses <code>source</code> for a number. This method can parse normal,
* numeric values as well as special values. These special values include
* Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY.
*
* @param source the string to parse
* @param format the number format used to parse normal, numeric values.
* @param pos input/ouput parsing parameter.
* @return the parsed number.
*/
public static Number parseNumber(final String source, final NumberFormat format,
final ParsePosition pos) {
final int startIndex = pos.getIndex();
Number number = format.parse(source, pos);
final int endIndex = pos.getIndex();
// check for error parsing number
if (startIndex == endIndex) {
// try parsing special numbers
final double[] special = {
Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY
};
for (int i = 0; i < special.length; ++i) {
number = parseNumber(source, special[i], pos);
if (number != null) {
break;
}
}
}
return number;
}
/**
* Parse <code>source</code> for an expected fixed string.
* @param source the string to parse
* @param expected expected string
* @param pos input/ouput parsing parameter.
* @return true if the expected string was there
*/
public static boolean parseFixedstring(final String source,
final String expected,
final ParsePosition pos) {
final int startIndex = pos.getIndex();
final int endIndex = startIndex + expected.length();
if ((startIndex >= source.length()) ||
(endIndex > source.length()) ||
(source.substring(startIndex, endIndex).compareTo(expected) != 0)) {
// set index back to start, error index should be the start index
pos.setIndex(startIndex);
pos.setErrorIndex(startIndex);
return false;
}
// the string was here
pos.setIndex(endIndex);
return true;
}
/**
* Formats a double value to produce a string. In general, the value is
* formatted using the formatting rules of <code>format</code>. There are
* three exceptions to this:
* <ol>
* <li>NaN is formatted as '(NaN)'</li>
* <li>Positive infinity is formatted as '(Infinity)'</li>
* <li>Negative infinity is formatted as '(-Infinity)'</li>
* </ol>
*
* @param value the double to format.
* @param format the format used.
* @param toAppendTo where the text is to be appended
* @param pos On input: an alignment field, if desired. On output: the
* offsets of the alignment field
* @return the value passed in as toAppendTo.
*/
public static StringBuffer formatDouble(final double value, final NumberFormat format,
final StringBuffer toAppendTo,
final FieldPosition pos) {
if( Double.isNaN(value) || Double.isInfinite(value) ) {
toAppendTo.append('(');
toAppendTo.append(value);
toAppendTo.append(')');
} else {
format.format(value, toAppendTo, pos);
}
return toAppendTo;
}
}