blob: 6521effa0a815fc9d33aba01929c60c7fc41050c [file] [log] [blame]
/*
*******************************************************************************
* Copyright (C) 2014, International Business Machines Corporation and
* others. All Rights Reserved.
*******************************************************************************
*/
package com.ibm.icu.text;
import java.text.AttributedCharacterIterator;
import java.text.AttributedCharacterIterator.Attribute;
import java.text.CharacterIterator;
import java.util.Map;
import com.ibm.icu.lang.UCharacter;
import com.ibm.icu.util.ULocale;
/**
*A formatter that formats numbers in user-friendly scientific notation.
*
* ScientificNumberFormatter instances are immutable and thread-safe.
*
* Sample code:
* <pre>
* ULocale en = new ULocale("en");
* ScientificNumberFormatter fmt = ScientificNumberFormatter.getMarkupInstance(
* en, "<sup>", "</sup>");
* <pre>
* // Output: "1.23456×10<sup>-78</sup>"
* System.out.println(fmt.format(1.23456e-78));
* </pre>
*
* @draft ICU 55
* @provisional This API might change or be removed in a future release.
*
*/
public final class ScientificNumberFormatter {
private final String preExponent;
private final DecimalFormat fmt;
private final Style style;
/**
* Gets a ScientificNumberFormatter instance that uses
* superscript characters for exponents for this locale.
* @param locale The locale
* @return The ScientificNumberFormatter instance.
*
* @draft ICU 55
* @provisional This API might change or be removed in a future release.
*/
public static ScientificNumberFormatter getSuperscriptInstance(ULocale locale) {
return getInstanceForLocale(locale, SUPER_SCRIPT);
}
/**
* Gets a ScientificNumberFormatter instance that uses
* superscript characters for exponents.
* @param df The DecimalFormat must be configured for scientific
* notation. Caller may safely change df after this call as this method
* clones it when creating the ScientificNumberFormatter.
* @return the ScientificNumberFormatter instance.
*
* @draft ICU 55
* @provisional This API might change or be removed in a future release.
*/
public static ScientificNumberFormatter getSuperscriptInstance(
DecimalFormat df) {
return getInstance(df, SUPER_SCRIPT);
}
/**
* Gets a ScientificNumberFormatter instance that uses
* markup for exponents for this locale.
* @param locale The locale
* @param beginMarkup the markup to start superscript e.g {@code <sup>}
* @param endMarkup the markup to end superscript e.g {@code </sup>}
* @return The ScientificNumberFormatter instance.
*
* @draft ICU 55
* @provisional This API might change or be removed in a future release.
*/
public static ScientificNumberFormatter getMarkupInstance(
ULocale locale,
String beginMarkup,
String endMarkup) {
return getInstanceForLocale(
locale, new MarkupStyle(beginMarkup, endMarkup));
}
/**
* Gets a ScientificNumberFormatter instance that uses
* markup for exponents.
* @param df The DecimalFormat must be configured for scientific
* notation. Caller may safely change df after this call as this method
* clones it when creating the ScientificNumberFormatter.
* @param beginMarkup the markup to start superscript e.g {@code <sup>}
* @param endMarkup the markup to end superscript e.g {@code </sup>}
* @return The ScientificNumberFormatter instance.
*
* @draft ICU 55
* @provisional This API might change or be removed in a future release.
*/
public static ScientificNumberFormatter getMarkupInstance(
DecimalFormat df,
String beginMarkup,
String endMarkup) {
return getInstance(
df, new MarkupStyle(beginMarkup, endMarkup));
}
/**
* Formats a number
* @param number Can be a double, int, Number or
* anything that DecimalFormat#format(Object) accepts.
* @return the formatted string.
*
* @draft ICU 55
* @provisional This API might change or be removed in a future release.
*/
public String format(Object number) {
synchronized (fmt) {
return style.format(
fmt.formatToCharacterIterator(number),
preExponent);
}
}
/**
* A style type for ScientificNumberFormatter. All Style instances are immutable
* and thread-safe.
*/
private static abstract class Style {
abstract String format(
AttributedCharacterIterator iterator,
String preExponent); // '* 10^'
static void append(
AttributedCharacterIterator iterator,
int start,
int limit,
StringBuilder result) {
int oldIndex = iterator.getIndex();
iterator.setIndex(start);
for (int i = start; i < limit; i++) {
result.append(iterator.current());
iterator.next();
}
iterator.setIndex(oldIndex);
}
}
private static class MarkupStyle extends Style {
private final String beginMarkup;
private final String endMarkup;
MarkupStyle(String beginMarkup, String endMarkup) {
this.beginMarkup = beginMarkup;
this.endMarkup = endMarkup;
}
@Override
String format(
AttributedCharacterIterator iterator,
String preExponent) {
int copyFromOffset = 0;
StringBuilder result = new StringBuilder();
for (
iterator.first();
iterator.current() != CharacterIterator.DONE;
) {
Map<Attribute, Object> attributeSet = iterator.getAttributes();
if (attributeSet.containsKey(NumberFormat.Field.EXPONENT_SYMBOL)) {
append(
iterator,
copyFromOffset,
iterator.getRunStart(NumberFormat.Field.EXPONENT_SYMBOL),
result);
copyFromOffset = iterator.getRunLimit(NumberFormat.Field.EXPONENT_SYMBOL);
iterator.setIndex(copyFromOffset);
result.append(preExponent);
result.append(beginMarkup);
} else if (attributeSet.containsKey(NumberFormat.Field.EXPONENT)) {
int limit = iterator.getRunLimit(NumberFormat.Field.EXPONENT);
append(
iterator,
copyFromOffset,
limit,
result);
copyFromOffset = limit;
iterator.setIndex(copyFromOffset);
result.append(endMarkup);
} else {
iterator.next();
}
}
append(iterator, copyFromOffset, iterator.getEndIndex(), result);
return result.toString();
}
}
private static class SuperscriptStyle extends Style {
private static final char[] SUPERSCRIPT_DIGITS = {
0x2070, 0xB9, 0xB2, 0xB3, 0x2074, 0x2075, 0x2076, 0x2077, 0x2078, 0x2079
};
private static final char SUPERSCRIPT_PLUS_SIGN = 0x207A;
private static final char SUPERSCRIPT_MINUS_SIGN = 0x207B;
@Override
String format(
AttributedCharacterIterator iterator,
String preExponent) {
int copyFromOffset = 0;
StringBuilder result = new StringBuilder();
for (
iterator.first();
iterator.current() != CharacterIterator.DONE;
) {
Map<Attribute, Object> attributeSet = iterator.getAttributes();
if (attributeSet.containsKey(NumberFormat.Field.EXPONENT_SYMBOL)) {
append(
iterator,
copyFromOffset,
iterator.getRunStart(NumberFormat.Field.EXPONENT_SYMBOL),
result);
copyFromOffset = iterator.getRunLimit(NumberFormat.Field.EXPONENT_SYMBOL);
iterator.setIndex(copyFromOffset);
result.append(preExponent);
} else if (attributeSet.containsKey(NumberFormat.Field.EXPONENT_SIGN)) {
int start = iterator.getRunStart(NumberFormat.Field.EXPONENT_SIGN);
int limit = iterator.getRunLimit(NumberFormat.Field.EXPONENT_SIGN);
int aChar = char32AtAndAdvance(iterator);
if (DecimalFormat.minusSigns.contains(aChar)) {
append(
iterator,
copyFromOffset,
start,
result);
result.append(SUPERSCRIPT_MINUS_SIGN);
} else if (DecimalFormat.plusSigns.contains(aChar)) {
append(
iterator,
copyFromOffset,
start,
result);
result.append(SUPERSCRIPT_PLUS_SIGN);
} else {
throw new IllegalArgumentException();
}
copyFromOffset = limit;
iterator.setIndex(copyFromOffset);
} else if (attributeSet.containsKey(NumberFormat.Field.EXPONENT)) {
int start = iterator.getRunStart(NumberFormat.Field.EXPONENT);
int limit = iterator.getRunLimit(NumberFormat.Field.EXPONENT);
append(
iterator,
copyFromOffset,
start,
result);
copyAsSuperscript(iterator, start, limit, result);
copyFromOffset = limit;
iterator.setIndex(copyFromOffset);
} else {
iterator.next();
}
}
append(iterator, copyFromOffset, iterator.getEndIndex(), result);
return result.toString();
}
private static void copyAsSuperscript(
AttributedCharacterIterator iterator, int start, int limit, StringBuilder result) {
int oldIndex = iterator.getIndex();
iterator.setIndex(start);
while (iterator.getIndex() < limit) {
int aChar = char32AtAndAdvance(iterator);
int digit = UCharacter.digit(aChar);
if (digit < 0) {
throw new IllegalArgumentException();
}
result.append(SUPERSCRIPT_DIGITS[digit]);
}
iterator.setIndex(oldIndex);
}
private static int char32AtAndAdvance(AttributedCharacterIterator iterator) {
char c1 = iterator.current();
char c2 = iterator.next();
if (UCharacter.isHighSurrogate(c1)) {
// If c2 is DONE, it will fail the low surrogate test and we
// skip this block.
if (UCharacter.isLowSurrogate(c2)) {
iterator.next();
return UCharacter.toCodePoint(c1, c2);
}
}
return c1;
}
}
private static String getPreExponent(DecimalFormatSymbols dfs) {
StringBuilder preExponent = new StringBuilder();
preExponent.append(dfs.getExponentMultiplicationSign());
char[] digits = dfs.getDigits();
preExponent.append(digits[1]).append(digits[0]);
return preExponent.toString();
}
private static ScientificNumberFormatter getInstance(
DecimalFormat decimalFormat, Style style) {
DecimalFormatSymbols dfs = decimalFormat.getDecimalFormatSymbols();
return new ScientificNumberFormatter(
(DecimalFormat) decimalFormat.clone(), getPreExponent(dfs), style);
}
private static ScientificNumberFormatter getInstanceForLocale(
ULocale locale, Style style) {
DecimalFormat decimalFormat =
(DecimalFormat) DecimalFormat.getScientificInstance(locale);
return new ScientificNumberFormatter(
decimalFormat,
getPreExponent(decimalFormat.getDecimalFormatSymbols()),
style);
}
private static final Style SUPER_SCRIPT = new SuperscriptStyle();
private ScientificNumberFormatter(
DecimalFormat decimalFormat, String preExponent, Style style) {
this.fmt = decimalFormat;
this.preExponent = preExponent;
this.style = style;
}
}