| // © 2016 and later: Unicode, Inc. and others. |
| // License & terms of use: http://www.unicode.org/copyright.html |
| /* |
| ******************************************************************************* |
| * Copyright (C) 2014-2016, 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.impl.StaticUnicodeSets; |
| 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> |
| * <pre> |
| * // Output: "1.23456×10<sup>-78</sup>" |
| * System.out.println(fmt.format(1.23456e-78)); |
| * </pre> |
| * |
| * @stable ICU 55 |
| * |
| */ |
| 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. |
| * |
| * @stable ICU 55 |
| */ |
| 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. |
| * |
| * @stable ICU 55 |
| */ |
| 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. |
| * |
| * @stable ICU 55 |
| */ |
| 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. |
| * |
| * @stable ICU 55 |
| */ |
| 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. |
| * |
| * @stable ICU 55 |
| */ |
| 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 (StaticUnicodeSets.get(StaticUnicodeSets.Key.MINUS_SIGN).contains(aChar)) { |
| append( |
| iterator, |
| copyFromOffset, |
| start, |
| result); |
| result.append(SUPERSCRIPT_MINUS_SIGN); |
| } else if (StaticUnicodeSets.get(StaticUnicodeSets.Key.PLUS_SIGN).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; |
| } |
| |
| |
| } |