-/*.jar
/.git
-/ShanesDecimalFormat
-/android
-/dcfmtb
-/dd1
icu4c/bin
icu4c/bin64
icu4c/include
icu4c/lib
icu4c/lib64
-icu4c/source/.vscode
icu4c/source/Doxyfile
icu4c/source/Makefile
icu4c/source/README
icu4c/source/allinone/ipch
icu4c/source/autom4te.cache
icu4c/source/bin
-icu4c/source/browse.VC.db
icu4c/source/common/*.ao
icu4c/source/common/*.d
icu4c/source/common/*.o
icu4c/source/layoutex/x86
icu4c/source/lib
icu4c/source/perf-*.xml
-icu4c/source/runShaneConfigureICU.sh
icu4c/source/samples/*.opensdf
icu4c/source/samples/*.sdf
icu4c/source/samples/Makefile
icu4c/source/samples/ustring/ustring.vcproj.*.*.user
icu4c/source/samples/ustring/x64
icu4c/source/samples/ustring/x86
-icu4c/source/shane.cpp
-icu4c/source/shane.exe
icu4c/source/stubdata/*.ao
icu4c/source/stubdata/*.d
icu4c/source/stubdata/*.o
icu4j/build-local.properties
icu4j/demos/out
icu4j/doc
-icu4j/eclipse
icu4j/eclipse-build/out
icu4j/lib/*.jar
icu4j/main/classes/charset/out
if (U_FAILURE(status)) {
return 0;
}
-
- // ICU 59 HACK: Ignore negative part of format string, mimicking ICU 58 behavior.
- // TODO(sffc): Make sure this is fixed during the overhaul port in ICU 60.
- int32_t semiPos = formatStr.indexOf(';', 0);
- if (semiPos == -1) {
- semiPos = formatStr.length();
- }
- UnicodeString positivePart = formatStr.tempSubString(0, semiPos);
-
- int32_t firstIdx = positivePart.indexOf(kZero, UPRV_LENGTHOF(kZero), 0);
+ int32_t firstIdx = formatStr.indexOf(kZero, UPRV_LENGTHOF(kZero), 0);
// We must have 0's in format string.
if (firstIdx == -1) {
status = U_INTERNAL_PROGRAM_ERROR;
return 0;
}
- int32_t lastIdx = positivePart.lastIndexOf(kZero, UPRV_LENGTHOF(kZero), firstIdx);
+ int32_t lastIdx = formatStr.lastIndexOf(kZero, UPRV_LENGTHOF(kZero), firstIdx);
CDFUnit* unit = createCDFUnit(variant, log10Value, result, status);
if (U_FAILURE(status)) {
return 0;
unit->markAsSet();
// Everything up to first 0 is the prefix
- unit->prefix = positivePart.tempSubString(0, firstIdx);
+ unit->prefix = formatStr.tempSubString(0, firstIdx);
fixQuotes(unit->prefix);
// Everything beyond the last 0 is the suffix
- unit->suffix = positivePart.tempSubString(lastIdx + 1);
+ unit->suffix = formatStr.tempSubString(lastIdx + 1);
fixQuotes(unit->suffix);
// If there is effectively no prefix or suffix, ignore the actual number of
// Calculate number of zeros before decimal point
int32_t idx = firstIdx + 1;
- while (idx <= lastIdx && positivePart.charAt(idx) == u_0) {
+ while (idx <= lastIdx && formatStr.charAt(idx) == u_0) {
++idx;
}
return (idx - firstIdx);
}
}
- /**
- * Creates an object that consumes code points one at a time and returns intermediate prefix
- * matches. Returns null if no match exists.
- *
- * @return An instance of {@link ParseState}, or null if the starting code point is not a
- * prefix for any entry in the trie.
- */
- public ParseState openParseState(int startingCp) {
- // Check to see whether this is a valid starting character. If not, return null.
- if (_ignoreCase) {
- startingCp = UCharacter.foldCase(startingCp, true);
- }
- int count = Character.charCount(startingCp);
- char ch1 = (count == 1) ? (char) startingCp : Character.highSurrogate(startingCp);
- if (!_root.hasChildFor(ch1)) {
- return null;
- }
-
- return new ParseState(_root);
- }
-
- /**
- * ParseState is mutable, not thread-safe, and intended to be used internally by parsers for
- * consuming values from this trie.
- */
- public class ParseState {
- private Node node;
- private int offset;
- private Node.StepResult result;
-
- ParseState(Node start) {
- node = start;
- offset = 0;
- result = start.new StepResult();
- }
-
- /**
- * Consumes a code point and walk to the next node in the trie.
- *
- * @param cp The code point to consume.
- */
- public void accept(int cp) {
- assert node != null;
- if (_ignoreCase) {
- cp = UCharacter.foldCase(cp, true);
- }
- int count = Character.charCount(cp);
- char ch1 = (count == 1) ? (char) cp : Character.highSurrogate(cp);
- node.takeStep(ch1, offset, result);
- if (count == 2 && result.node != null) {
- char ch2 = Character.lowSurrogate(cp);
- result.node.takeStep(ch2, result.offset, result);
- }
- node = result.node;
- offset = result.offset;
- }
-
- /**
- * Gets the exact prefix matches for all code points that have been consumed so far.
- *
- * @return The matches.
- */
- public Iterator<V> getCurrentMatches() {
- if (node != null && offset == node.charCount()) {
- return node.values();
- }
- return null;
- }
-
- /**
- * Checks whether any more code points can be consumed.
- *
- * @return true if no more code points can be consumed; false otherwise.
- */
- public boolean atEnd() {
- return node == null || (node.charCount() == offset && node._children == null);
- }
- }
-
public static class CharIterator implements Iterator<Character> {
private boolean _ignoreCase;
private CharSequence _text;
_children = children;
}
- public int charCount() {
- return _text == null ? 0 : _text.length;
- }
-
- public boolean hasChildFor(char ch) {
- for (int i=0; _children != null && i < _children.size(); i++) {
- Node child = _children.get(i);
- if (ch < child._text[0]) break;
- if (ch == child._text[0]) {
- return true;
- }
- }
- return false;
- }
-
public Iterator<V> values() {
if (_values == null) {
return null;
return match;
}
- public class StepResult {
- public Node node;
- public int offset;
- }
- public void takeStep(char ch, int offset, StepResult result) {
- assert offset <= charCount();
- if (offset == charCount()) {
- // Go to a child node
- for (int i=0; _children != null && i < _children.size(); i++) {
- Node child = _children.get(i);
- if (ch < child._text[0]) break;
- if (ch == child._text[0]) {
- // Found a matching child node
- result.node = child;
- result.offset = 1;
- return;
- }
- }
- // No matching children; fall through
- } else if (_text[offset] == ch) {
- // Return to this node; increase offset
- result.node = this;
- result.offset = offset + 1;
- return;
- }
- // No matches
- result.node = null;
- result.offset = -1;
- return;
- }
-
private void add(char[] text, int offset, V value) {
if (text.length == offset) {
_values = addValue(_values, value);
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number;
-
-import com.ibm.icu.text.DecimalFormatSymbols;
-import com.ibm.icu.text.NumberFormat.Field;
-
-/**
- * Performs manipulations on affix patterns: the prefix and suffix strings associated with a decimal
- * format pattern. For example:
- *
- * <table>
- * <tr><th>Affix Pattern</th><th>Example Unescaped (Formatted) String</th></tr>
- * <tr><td>abc</td><td>abc</td></tr>
- * <tr><td>ab-</td><td>ab−</td></tr>
- * <tr><td>ab'-'</td><td>ab-</td></tr>
- * <tr><td>ab''</td><td>ab'</td></tr>
- * </table>
- *
- * To manually iterate over tokens in a literal string, use the following pattern, which is designed
- * to be efficient.
- *
- * <pre>
- * long tag = 0L;
- * while (AffixPatternUtils.hasNext(tag, patternString)) {
- * tag = AffixPatternUtils.nextToken(tag, patternString);
- * int typeOrCp = AffixPatternUtils.getTypeOrCp(tag);
- * switch (typeOrCp) {
- * case AffixPatternUtils.TYPE_MINUS_SIGN:
- * // Current token is a minus sign.
- * break;
- * case AffixPatternUtils.TYPE_PLUS_SIGN:
- * // Current token is a plus sign.
- * break;
- * case AffixPatternUtils.TYPE_PERCENT:
- * // Current token is a percent sign.
- * break;
- * case AffixPatternUtils.TYPE_PERMILLE:
- * // Current token is a permille sign.
- * break;
- * case AffixPatternUtils.TYPE_CURRENCY_SINGLE:
- * // Current token is a single currency sign.
- * break;
- * case AffixPatternUtils.TYPE_CURRENCY_DOUBLE:
- * // Current token is a double currency sign.
- * break;
- * case AffixPatternUtils.TYPE_CURRENCY_TRIPLE:
- * // Current token is a triple currency sign.
- * break;
- * case AffixPatternUtils.TYPE_CURRENCY_OVERFLOW:
- * // Current token has four or more currency signs.
- * break;
- * default:
- * // Current token is an arbitrary code point.
- * // The variable typeOrCp is the code point.
- * break;
- * }
- * }
- * </pre>
- */
-public class AffixPatternUtils {
-
- private static final int STATE_BASE = 0;
- private static final int STATE_FIRST_QUOTE = 1;
- private static final int STATE_INSIDE_QUOTE = 2;
- private static final int STATE_AFTER_QUOTE = 3;
- private static final int STATE_FIRST_CURR = 4;
- private static final int STATE_SECOND_CURR = 5;
- private static final int STATE_THIRD_CURR = 6;
- private static final int STATE_OVERFLOW_CURR = 7;
-
- private static final int TYPE_CODEPOINT = 0;
-
- /** Represents a minus sign symbol '-'. */
- public static final int TYPE_MINUS_SIGN = -1;
-
- /** Represents a plus sign symbol '+'. */
- public static final int TYPE_PLUS_SIGN = -2;
-
- /** Represents a percent sign symbol '%'. */
- public static final int TYPE_PERCENT = -3;
-
- /** Represents a permille sign symbol '‰'. */
- public static final int TYPE_PERMILLE = -4;
-
- /** Represents a single currency symbol '¤'. */
- public static final int TYPE_CURRENCY_SINGLE = -5;
-
- /** Represents a double currency symbol '¤¤'. */
- public static final int TYPE_CURRENCY_DOUBLE = -6;
-
- /** Represents a triple currency symbol '¤¤¤'. */
- public static final int TYPE_CURRENCY_TRIPLE = -7;
-
- /** Represents a sequence of four or more currency symbols. */
- public static final int TYPE_CURRENCY_OVERFLOW = -15;
-
- /**
- * Checks whether the specified affix pattern has any unquoted currency symbols ("¤").
- *
- * @param patternString The string to check for currency symbols.
- * @return true if the literal has at least one unquoted currency symbol; false otherwise.
- */
- public static boolean hasCurrencySymbols(CharSequence patternString) {
- if (patternString == null) return false;
- int offset = 0;
- int state = STATE_BASE;
- boolean result = false;
- for (; offset < patternString.length(); ) {
- int cp = Character.codePointAt(patternString, offset);
- switch (state) {
- case STATE_BASE:
- if (cp == '¤') {
- result = true;
- } else if (cp == '\'') {
- state = STATE_INSIDE_QUOTE;
- }
- break;
- case STATE_INSIDE_QUOTE:
- if (cp == '\'') {
- state = STATE_BASE;
- }
- break;
- default:
- throw new AssertionError();
- }
- offset += Character.charCount(cp);
- }
-
- if (state == STATE_INSIDE_QUOTE) {
- throw new IllegalArgumentException("Unterminated quote: \"" + patternString + "\"");
- } else {
- return result;
- }
- }
-
- /**
- * Estimates the number of code points present in an unescaped version of the affix pattern string
- * (one that would be returned by {@link #unescape}), assuming that all interpolated symbols
- * consume one code point and that currencies consume as many code points as their symbol width.
- * Used for computing padding width.
- *
- * @param patternString The original string whose width will be estimated.
- * @return The length of the unescaped string.
- */
- public static int unescapedLength(CharSequence patternString) {
- if (patternString == null) return 0;
- int state = STATE_BASE;
- int offset = 0;
- int length = 0;
- for (; offset < patternString.length(); ) {
- int cp = Character.codePointAt(patternString, offset);
-
- switch (state) {
- case STATE_BASE:
- if (cp == '\'') {
- // First quote
- state = STATE_FIRST_QUOTE;
- } else {
- // Unquoted symbol
- length++;
- }
- break;
- case STATE_FIRST_QUOTE:
- if (cp == '\'') {
- // Repeated quote
- length++;
- state = STATE_BASE;
- } else {
- // Quoted code point
- length++;
- state = STATE_INSIDE_QUOTE;
- }
- break;
- case STATE_INSIDE_QUOTE:
- if (cp == '\'') {
- // End of quoted sequence
- state = STATE_AFTER_QUOTE;
- } else {
- // Quoted code point
- length++;
- }
- break;
- case STATE_AFTER_QUOTE:
- if (cp == '\'') {
- // Double quote inside of quoted sequence
- length++;
- state = STATE_INSIDE_QUOTE;
- } else {
- // Unquoted symbol
- length++;
- }
- break;
- default:
- throw new AssertionError();
- }
-
- offset += Character.charCount(cp);
- }
-
- switch (state) {
- case STATE_FIRST_QUOTE:
- case STATE_INSIDE_QUOTE:
- throw new IllegalArgumentException("Unterminated quote: \"" + patternString + "\"");
- default:
- break;
- }
-
- return length;
- }
-
- /**
- * Takes a string and escapes (quotes) characters that have special meaning in the affix pattern
- * syntax. This function does not reverse-lookup symbols.
- *
- * <p>Example input: "-$x"; example output: "'-'$x"
- *
- * @param input The string to be escaped.
- * @param output The string builder to which to append the escaped string.
- * @return The number of chars (UTF-16 code units) appended to the output.
- */
- public static int escape(CharSequence input, StringBuilder output) {
- if (input == null) return 0;
- int state = STATE_BASE;
- int offset = 0;
- int startLength = output.length();
- for (; offset < input.length(); ) {
- int cp = Character.codePointAt(input, offset);
-
- switch (cp) {
- case '\'':
- output.append("''");
- break;
-
- case '-':
- case '+':
- case '%':
- case '‰':
- case '¤':
- if (state == STATE_BASE) {
- output.append('\'');
- output.appendCodePoint(cp);
- state = STATE_INSIDE_QUOTE;
- } else {
- output.appendCodePoint(cp);
- }
- break;
-
- default:
- if (state == STATE_INSIDE_QUOTE) {
- output.append('\'');
- output.appendCodePoint(cp);
- state = STATE_BASE;
- } else {
- output.appendCodePoint(cp);
- }
- break;
- }
- offset += Character.charCount(cp);
- }
-
- if (state == STATE_INSIDE_QUOTE) {
- output.append('\'');
- }
-
- return output.length() - startLength;
- }
-
- /**
- * Executes the unescape state machine. Replaces the unquoted characters "-", "+", "%", and "‰"
- * with their localized equivalents. Replaces "¤", "¤¤", and "¤¤¤" with the three argument
- * strings.
- *
- * <p>Example input: "'-'¤x"; example output: "-$x"
- *
- * @param affixPattern The original string to be unescaped.
- * @param symbols An instance of {@link DecimalFormatSymbols} for the locale of interest.
- * @param currency1 The string to replace "¤".
- * @param currency2 The string to replace "¤¤".
- * @param currency3 The string to replace "¤¤¤".
- * @param minusSign The string to replace "-". If null, symbols.getMinusSignString() is used.
- * @param output The {@link NumberStringBuilder} to which the result will be appended.
- */
- public static void unescape(
- CharSequence affixPattern,
- DecimalFormatSymbols symbols,
- String currency1,
- String currency2,
- String currency3,
- String minusSign,
- NumberStringBuilder output) {
- if (affixPattern == null || affixPattern.length() == 0) return;
- if (minusSign == null) minusSign = symbols.getMinusSignString();
- long tag = 0L;
- while (hasNext(tag, affixPattern)) {
- tag = nextToken(tag, affixPattern);
- int typeOrCp = getTypeOrCp(tag);
- switch (typeOrCp) {
- case TYPE_MINUS_SIGN:
- output.append(minusSign, Field.SIGN);
- break;
- case TYPE_PLUS_SIGN:
- output.append(symbols.getPlusSignString(), Field.SIGN);
- break;
- case TYPE_PERCENT:
- output.append(symbols.getPercentString(), Field.PERCENT);
- break;
- case TYPE_PERMILLE:
- output.append(symbols.getPerMillString(), Field.PERMILLE);
- break;
- case TYPE_CURRENCY_SINGLE:
- output.append(currency1, Field.CURRENCY);
- break;
- case TYPE_CURRENCY_DOUBLE:
- output.append(currency2, Field.CURRENCY);
- break;
- case TYPE_CURRENCY_TRIPLE:
- output.append(currency3, Field.CURRENCY);
- break;
- case TYPE_CURRENCY_OVERFLOW:
- output.append("\uFFFD", Field.CURRENCY);
- break;
- default:
- output.appendCodePoint(typeOrCp, null);
- break;
- }
- }
- }
-
- /**
- * Returns the next token from the affix pattern.
- *
- * @param tag A bitmask used for keeping track of state from token to token. The initial value
- * should be 0L.
- * @param patternString The affix pattern.
- * @return The bitmask tag to pass to the next call of this method to retrieve the following
- * token.
- * @throws IllegalArgumentException If there are no tokens left or if there is a syntax error in
- * the pattern string.
- * @see #hasNext
- */
- public static long nextToken(long tag, CharSequence patternString) {
- int offset = getOffset(tag);
- int state = getState(tag);
- for (; offset < patternString.length(); ) {
- int cp = Character.codePointAt(patternString, offset);
- int count = Character.charCount(cp);
-
- switch (state) {
- case STATE_BASE:
- switch (cp) {
- case '\'':
- state = STATE_FIRST_QUOTE;
- offset += count;
- // continue to the next code point
- break;
- case '-':
- return makeTag(offset + count, TYPE_MINUS_SIGN, STATE_BASE, 0);
- case '+':
- return makeTag(offset + count, TYPE_PLUS_SIGN, STATE_BASE, 0);
- case '%':
- return makeTag(offset + count, TYPE_PERCENT, STATE_BASE, 0);
- case '‰':
- return makeTag(offset + count, TYPE_PERMILLE, STATE_BASE, 0);
- case '¤':
- state = STATE_FIRST_CURR;
- offset += count;
- // continue to the next code point
- break;
- default:
- return makeTag(offset + count, TYPE_CODEPOINT, STATE_BASE, cp);
- }
- break;
- case STATE_FIRST_QUOTE:
- if (cp == '\'') {
- return makeTag(offset + count, TYPE_CODEPOINT, STATE_BASE, cp);
- } else {
- return makeTag(offset + count, TYPE_CODEPOINT, STATE_INSIDE_QUOTE, cp);
- }
- case STATE_INSIDE_QUOTE:
- if (cp == '\'') {
- state = STATE_AFTER_QUOTE;
- offset += count;
- // continue to the next code point
- break;
- } else {
- return makeTag(offset + count, TYPE_CODEPOINT, STATE_INSIDE_QUOTE, cp);
- }
- case STATE_AFTER_QUOTE:
- if (cp == '\'') {
- return makeTag(offset + count, TYPE_CODEPOINT, STATE_INSIDE_QUOTE, cp);
- } else {
- state = STATE_BASE;
- // re-evaluate this code point
- break;
- }
- case STATE_FIRST_CURR:
- if (cp == '¤') {
- state = STATE_SECOND_CURR;
- offset += count;
- // continue to the next code point
- break;
- } else {
- return makeTag(offset, TYPE_CURRENCY_SINGLE, STATE_BASE, 0);
- }
- case STATE_SECOND_CURR:
- if (cp == '¤') {
- state = STATE_THIRD_CURR;
- offset += count;
- // continue to the next code point
- break;
- } else {
- return makeTag(offset, TYPE_CURRENCY_DOUBLE, STATE_BASE, 0);
- }
- case STATE_THIRD_CURR:
- if (cp == '¤') {
- state = STATE_OVERFLOW_CURR;
- offset += count;
- // continue to the next code point
- break;
- } else {
- return makeTag(offset, TYPE_CURRENCY_TRIPLE, STATE_BASE, 0);
- }
- case STATE_OVERFLOW_CURR:
- if (cp == '¤') {
- offset += count;
- // continue to the next code point and loop back to this state
- break;
- } else {
- return makeTag(offset, TYPE_CURRENCY_OVERFLOW, STATE_BASE, 0);
- }
- default:
- throw new AssertionError();
- }
- }
- // End of string
- switch (state) {
- case STATE_BASE:
- // We shouldn't get here if hasNext() was followed.
- throw new IllegalArgumentException();
- case STATE_FIRST_QUOTE:
- case STATE_INSIDE_QUOTE:
- // For consistent behavior with the JDK and ICU 58, throw an exception here.
- throw new IllegalArgumentException(
- "Unterminated quote in pattern affix: \"" + patternString + "\"");
- case STATE_AFTER_QUOTE:
- // We shouldn't get here if hasNext() was followed.
- throw new IllegalArgumentException();
- case STATE_FIRST_CURR:
- return makeTag(offset, TYPE_CURRENCY_SINGLE, STATE_BASE, 0);
- case STATE_SECOND_CURR:
- return makeTag(offset, TYPE_CURRENCY_DOUBLE, STATE_BASE, 0);
- case STATE_THIRD_CURR:
- return makeTag(offset, TYPE_CURRENCY_TRIPLE, STATE_BASE, 0);
- case STATE_OVERFLOW_CURR:
- return makeTag(offset, TYPE_CURRENCY_OVERFLOW, STATE_BASE, 0);
- default:
- throw new AssertionError();
- }
- }
-
- /**
- * Returns whether the affix pattern string has any more tokens to be retrieved from a call to
- * {@link #nextToken}.
- *
- * @param tag The bitmask tag of the previous token, as returned by {@link #nextToken}.
- * @param string The affix pattern.
- * @return true if there are more tokens to consume; false otherwise.
- */
- public static boolean hasNext(long tag, CharSequence string) {
- int state = getState(tag);
- int offset = getOffset(tag);
- // Special case: the last character in string is an end quote.
- if (state == STATE_INSIDE_QUOTE
- && offset == string.length() - 1
- && string.charAt(offset) == '\'') {
- return false;
- } else if (state != STATE_BASE) {
- return true;
- } else {
- return offset < string.length();
- }
- }
-
- /**
- * This function helps determine the identity of the token consumed by {@link #nextToken}.
- * Converts from a bitmask tag, based on a call to {@link #nextToken}, to its corresponding symbol
- * type or code point.
- *
- * @param tag The bitmask tag of the current token, as returned by {@link #nextToken}.
- * @return If less than zero, a symbol type corresponding to one of the <code>TYPE_</code>
- * constants, such as {@link #TYPE_MINUS_SIGN}. If greater than or equal to zero, a literal
- * code point.
- */
- public static int getTypeOrCp(long tag) {
- int type = getType(tag);
- return (type == 0) ? getCodePoint(tag) : -type;
- }
-
- private static long makeTag(int offset, int type, int state, int cp) {
- long tag = 0L;
- tag |= offset;
- tag |= (-(long) type) << 32;
- tag |= ((long) state) << 36;
- tag |= ((long) cp) << 40;
- return tag;
- }
-
- static int getOffset(long tag) {
- return (int) (tag & 0xffffffff);
- }
-
- static int getType(long tag) {
- return (int) ((tag >>> 32) & 0xf);
- }
-
- static int getState(long tag) {
- return (int) ((tag >>> 36) & 0xf);
- }
-
- static int getCodePoint(long tag) {
- return (int) (tag >>> 40);
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number;
-
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.Map;
-
-import com.ibm.icu.impl.number.Format.BeforeTargetAfterFormat;
-import com.ibm.icu.impl.number.Format.SingularFormat;
-import com.ibm.icu.impl.number.Format.TargetFormat;
-import com.ibm.icu.impl.number.formatters.BigDecimalMultiplier;
-import com.ibm.icu.impl.number.formatters.CompactDecimalFormat;
-import com.ibm.icu.impl.number.formatters.CurrencyFormat;
-import com.ibm.icu.impl.number.formatters.MagnitudeMultiplier;
-import com.ibm.icu.impl.number.formatters.MeasureFormat;
-import com.ibm.icu.impl.number.formatters.PaddingFormat;
-import com.ibm.icu.impl.number.formatters.PositiveDecimalFormat;
-import com.ibm.icu.impl.number.formatters.PositiveNegativeAffixFormat;
-import com.ibm.icu.impl.number.formatters.RoundingFormat;
-import com.ibm.icu.impl.number.formatters.ScientificFormat;
-import com.ibm.icu.text.DecimalFormatSymbols;
-import com.ibm.icu.text.PluralRules;
-import com.ibm.icu.util.ULocale;
-
-public class Endpoint {
- // public static Format from(DecimalFormatSymbols symbols, Properties properties)
- // throws ParseException {
- // Format format = new PositiveIntegerFormat(symbols, properties);
- // // TODO: integer-only format
- // format = new PositiveDecimalFormat((SelfContainedFormat) format, symbols, properties);
- // if (properties.useCompactDecimalFormat()) {
- // format = CompactDecimalFormat.getInstance((SingularFormat) format, symbols, properties);
- // } else {
- // format =
- // PositiveNegativeAffixFormat.getInstance((SingularFormat) format, symbols, properties);
- // }
- // if (properties.useRoundingInterval()) {
- // format = new IntervalRoundingFormat((SingularFormat) format, properties);
- // } else if (properties.useSignificantDigits()) {
- // format = new SignificantDigitsFormat((SingularFormat) format, properties);
- // } else if (properties.useFractionFormat()) {
- // format = new RoundingFormat((SingularFormat) format, properties);
- // }
- // return format;
- // }
-
- public static Format fromBTA(Properties properties) {
- return fromBTA(properties, getSymbols());
- }
-
- public static SingularFormat fromBTA(Properties properties, Locale locale) {
- return fromBTA(properties, getSymbols(locale));
- }
-
- public static SingularFormat fromBTA(Properties properties, ULocale uLocale) {
- return fromBTA(properties, getSymbols(uLocale));
- }
-
- public static SingularFormat fromBTA(String pattern) {
- return fromBTA(getProperties(pattern), getSymbols());
- }
-
- public static SingularFormat fromBTA(String pattern, Locale locale) {
- return fromBTA(getProperties(pattern), getSymbols(locale));
- }
-
- public static SingularFormat fromBTA(String pattern, ULocale uLocale) {
- return fromBTA(getProperties(pattern), getSymbols(uLocale));
- }
-
- public static SingularFormat fromBTA(String pattern, DecimalFormatSymbols symbols) {
- return fromBTA(getProperties(pattern), symbols);
- }
-
- public static SingularFormat fromBTA(Properties properties, DecimalFormatSymbols symbols) {
-
- if (symbols == null) throw new IllegalArgumentException("symbols must not be null");
-
- // TODO: This fast track results in an improvement of about 10ns during formatting. See if
- // there is a way to implement it more elegantly.
- boolean canUseFastTrack = true;
- PluralRules rules = getPluralRules(symbols.getULocale(), properties);
- BeforeTargetAfterFormat format = new Format.BeforeTargetAfterFormat(rules);
- TargetFormat target = new PositiveDecimalFormat(symbols, properties);
- format.setTargetFormat(target);
- // TODO: integer-only format?
- if (MagnitudeMultiplier.useMagnitudeMultiplier(properties)) {
- canUseFastTrack = false;
- format.addBeforeFormat(MagnitudeMultiplier.getInstance(properties));
- }
- if (BigDecimalMultiplier.useMultiplier(properties)) {
- canUseFastTrack = false;
- format.addBeforeFormat(BigDecimalMultiplier.getInstance(properties));
- }
- if (MeasureFormat.useMeasureFormat(properties)) {
- canUseFastTrack = false;
- format.addBeforeFormat(MeasureFormat.getInstance(symbols, properties));
- }
- if (CurrencyFormat.useCurrency(properties)) {
- canUseFastTrack = false;
- if (CompactDecimalFormat.useCompactDecimalFormat(properties)) {
- format.addBeforeFormat(CompactDecimalFormat.getInstance(symbols, properties));
- } else if (ScientificFormat.useScientificNotation(properties)) {
- // TODO: Should the currency rounder or scientific rounder be used in this case?
- // For now, default to using the scientific rounder.
- format.addBeforeFormat(PositiveNegativeAffixFormat.getInstance(symbols, properties));
- format.addBeforeFormat(ScientificFormat.getInstance(symbols, properties));
- } else {
- format.addBeforeFormat(CurrencyFormat.getCurrencyRounder(symbols, properties));
- format.addBeforeFormat(CurrencyFormat.getCurrencyModifier(symbols, properties));
- }
- } else {
- if (CompactDecimalFormat.useCompactDecimalFormat(properties)) {
- canUseFastTrack = false;
- format.addBeforeFormat(CompactDecimalFormat.getInstance(symbols, properties));
- } else if (ScientificFormat.useScientificNotation(properties)) {
- canUseFastTrack = false;
- format.addBeforeFormat(PositiveNegativeAffixFormat.getInstance(symbols, properties));
- format.addBeforeFormat(ScientificFormat.getInstance(symbols, properties));
- } else {
- format.addBeforeFormat(PositiveNegativeAffixFormat.getInstance(symbols, properties));
- format.addBeforeFormat(RoundingFormat.getDefaultOrNoRounder(properties));
- }
- }
- if (PaddingFormat.usePadding(properties)) {
- canUseFastTrack = false;
- format.addAfterFormat(PaddingFormat.getInstance(properties));
- }
- if (canUseFastTrack) {
- return new Format.PositiveNegativeRounderTargetFormat(
- PositiveNegativeAffixFormat.getInstance(symbols, properties),
- RoundingFormat.getDefaultOrNoRounder(properties),
- target);
- } else {
- return format;
- }
- }
-
- public static String staticFormat(FormatQuantity input, Properties properties) {
- return staticFormat(input, properties, getSymbols());
- }
-
- public static String staticFormat(FormatQuantity input, Properties properties, Locale locale) {
- return staticFormat(input, properties, getSymbols(locale));
- }
-
- public static String staticFormat(FormatQuantity input, Properties properties, ULocale uLocale) {
- return staticFormat(input, properties, getSymbols(uLocale));
- }
-
- public static String staticFormat(FormatQuantity input, String pattern) {
- return staticFormat(input, getProperties(pattern), getSymbols());
- }
-
- public static String staticFormat(FormatQuantity input, String pattern, Locale locale) {
- return staticFormat(input, getProperties(pattern), getSymbols(locale));
- }
-
- public static String staticFormat(FormatQuantity input, String pattern, ULocale uLocale) {
- return staticFormat(input, getProperties(pattern), getSymbols(uLocale));
- }
-
- public static String staticFormat(
- FormatQuantity input, String pattern, DecimalFormatSymbols symbols) {
- return staticFormat(input, getProperties(pattern), symbols);
- }
-
- public static String staticFormat(
- FormatQuantity input, Properties properties, DecimalFormatSymbols symbols) {
- PluralRules rules = null;
- ModifierHolder mods = Format.threadLocalModifierHolder.get().clear();
- NumberStringBuilder sb = Format.threadLocalStringBuilder.get().clear();
- int length = 0;
-
- // Pre-processing
- if (!input.isNaN()) {
- if (MagnitudeMultiplier.useMagnitudeMultiplier(properties)) {
- MagnitudeMultiplier.getInstance(properties).before(input, mods, rules);
- }
- if (BigDecimalMultiplier.useMultiplier(properties)) {
- BigDecimalMultiplier.getInstance(properties).before(input, mods, rules);
- }
- if (MeasureFormat.useMeasureFormat(properties)) {
- rules = (rules != null) ? rules : getPluralRules(symbols.getULocale(), properties);
- MeasureFormat.getInstance(symbols, properties).before(input, mods, rules);
- }
- if (CompactDecimalFormat.useCompactDecimalFormat(properties)) {
- rules = (rules != null) ? rules : getPluralRules(symbols.getULocale(), properties);
- CompactDecimalFormat.apply(input, mods, rules, symbols, properties);
- } else if (CurrencyFormat.useCurrency(properties)) {
- rules = (rules != null) ? rules : getPluralRules(symbols.getULocale(), properties);
- CurrencyFormat.getCurrencyRounder(symbols, properties).before(input, mods, rules);
- CurrencyFormat.getCurrencyModifier(symbols, properties).before(input, mods, rules);
- } else if (ScientificFormat.useScientificNotation(properties)) {
- // TODO: Is it possible to combine significant digits with currency?
- PositiveNegativeAffixFormat.getInstance(symbols, properties).before(input, mods, rules);
- ScientificFormat.getInstance(symbols, properties).before(input, mods, rules);
- } else {
- PositiveNegativeAffixFormat.apply(input, mods, symbols, properties);
- RoundingFormat.getDefaultOrNoRounder(properties).before(input, mods, rules);
- }
- }
-
- // Primary format step
- length += new PositiveDecimalFormat(symbols, properties).target(input, sb, 0);
- length += mods.applyStrong(sb, 0, length);
-
- // Post-processing
- if (PaddingFormat.usePadding(properties)) {
- length += PaddingFormat.getInstance(properties).after(mods, sb, 0, length);
- }
- length += mods.applyAll(sb, 0, length);
-
- return sb.toString();
- }
-
- private static final ThreadLocal<Map<ULocale, DecimalFormatSymbols>> threadLocalSymbolsCache =
- new ThreadLocal<Map<ULocale, DecimalFormatSymbols>>() {
- @Override
- protected Map<ULocale, DecimalFormatSymbols> initialValue() {
- return new HashMap<ULocale, DecimalFormatSymbols>();
- }
- };
-
- private static DecimalFormatSymbols getSymbols() {
- ULocale uLocale = ULocale.getDefault();
- return getSymbols(uLocale);
- }
-
- private static DecimalFormatSymbols getSymbols(Locale locale) {
- ULocale uLocale = ULocale.forLocale(locale);
- return getSymbols(uLocale);
- }
-
- private static DecimalFormatSymbols getSymbols(ULocale uLocale) {
- if (uLocale == null) uLocale = ULocale.getDefault();
- DecimalFormatSymbols symbols = threadLocalSymbolsCache.get().get(uLocale);
- if (symbols == null) {
- symbols = DecimalFormatSymbols.getInstance(uLocale);
- threadLocalSymbolsCache.get().put(uLocale, symbols);
- }
- return symbols;
- }
-
- private static final ThreadLocal<Map<String, Properties>> threadLocalPropertiesCache =
- new ThreadLocal<Map<String, Properties>>() {
- @Override
- protected Map<String, Properties> initialValue() {
- return new HashMap<String, Properties>();
- }
- };
-
- private static Properties getProperties(String pattern) {
- if (pattern == null) pattern = "#";
- Properties properties = threadLocalPropertiesCache.get().get(pattern);
- if (properties == null) {
- properties = PatternString.parseToProperties(pattern);
- threadLocalPropertiesCache.get().put(pattern.intern(), properties);
- }
- return properties;
- }
-
- private static final ThreadLocal<Map<ULocale, PluralRules>> threadLocalRulesCache =
- new ThreadLocal<Map<ULocale, PluralRules>>() {
- @Override
- protected Map<ULocale, PluralRules> initialValue() {
- return new HashMap<ULocale, PluralRules>();
- }
- };
-
- private static PluralRules getPluralRules(ULocale uLocale, Properties properties) {
- // Backwards compatibility: CurrencyPluralInfo wraps its own copy of PluralRules
- if (properties.getCurrencyPluralInfo() != null) {
- return properties.getCurrencyPluralInfo().getPluralRules();
- }
-
- if (uLocale == null) uLocale = ULocale.getDefault();
- PluralRules rules = threadLocalRulesCache.get().get(uLocale);
- if (rules == null) {
- rules = PluralRules.forLocale(uLocale);
- threadLocalRulesCache.get().put(uLocale, rules);
- }
- return rules;
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number;
-
-/**
- * This is a small interface I made to assist with converting from a formatter pipeline object to a
- * pattern string. It allows classes to "export" themselves to a property bag, which in turn can be
- * passed to {@link PatternString#propertiesToString(Properties)} to generate the pattern string.
- *
- * <p>Depending on the new API we expose, this process might not be necessary if we persist the
- * property bag in the current DecimalFormat shim.
- */
-public interface Exportable {
- public void export(Properties properties);
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number;
-
-import java.text.AttributedCharacterIterator;
-import java.text.FieldPosition;
-import java.util.ArrayDeque;
-import java.util.Arrays;
-import java.util.Deque;
-
-import com.ibm.icu.text.PluralRules;
-
-// TODO: Get a better name for this base class.
-public abstract class Format {
-
- protected static final ThreadLocal<NumberStringBuilder> threadLocalStringBuilder =
- new ThreadLocal<NumberStringBuilder>() {
- @Override
- protected NumberStringBuilder initialValue() {
- return new NumberStringBuilder();
- }
- };
-
- protected static final ThreadLocal<ModifierHolder> threadLocalModifierHolder =
- new ThreadLocal<ModifierHolder>() {
- @Override
- protected ModifierHolder initialValue() {
- return new ModifierHolder();
- }
- };
-
- public String format(FormatQuantity... inputs) {
- // Setup
- Deque<FormatQuantity> inputDeque = new ArrayDeque<FormatQuantity>();
- inputDeque.addAll(Arrays.asList(inputs));
- ModifierHolder modDeque = threadLocalModifierHolder.get().clear();
- NumberStringBuilder sb = threadLocalStringBuilder.get().clear();
-
- // Primary "recursion" step, calling the implementation's process method
- int length = process(inputDeque, modDeque, sb, 0);
-
- // Resolve remaining affixes
- length += modDeque.applyAll(sb, 0, length);
- return sb.toString();
- }
-
- /** A Format that works on only one number. */
- public abstract static class SingularFormat extends Format implements Exportable {
-
- public String format(FormatQuantity input) {
- NumberStringBuilder sb = formatToStringBuilder(input);
- return sb.toString();
- }
-
- public void format(FormatQuantity input, StringBuffer output) {
- NumberStringBuilder sb = formatToStringBuilder(input);
- output.append(sb);
- }
-
- public String format(FormatQuantity input, FieldPosition fp) {
- NumberStringBuilder sb = formatToStringBuilder(input);
- sb.populateFieldPosition(fp, 0);
- return sb.toString();
- }
-
- public void format(FormatQuantity input, StringBuffer output, FieldPosition fp) {
- NumberStringBuilder sb = formatToStringBuilder(input);
- sb.populateFieldPosition(fp, output.length());
- output.append(sb);
- }
-
- public AttributedCharacterIterator formatToCharacterIterator(FormatQuantity input) {
- NumberStringBuilder sb = formatToStringBuilder(input);
- return sb.getIterator();
- }
-
- private NumberStringBuilder formatToStringBuilder(FormatQuantity input) {
- // Setup
- ModifierHolder modDeque = threadLocalModifierHolder.get().clear();
- NumberStringBuilder sb = threadLocalStringBuilder.get().clear();
-
- // Primary "recursion" step, calling the implementation's process method
- int length = process(input, modDeque, sb, 0);
-
- // Resolve remaining affixes
- length += modDeque.applyAll(sb, 0, length);
- return sb;
- }
-
- @Override
- public int process(
- Deque<FormatQuantity> input,
- ModifierHolder mods,
- NumberStringBuilder string,
- int startIndex) {
- return process(input.removeFirst(), mods, string, startIndex);
- }
-
- public abstract int process(
- FormatQuantity input, ModifierHolder mods, NumberStringBuilder string, int startIndex);
- }
-
- public static class BeforeTargetAfterFormat extends SingularFormat {
- // The formatters are kept as individual fields to avoid extra object creation overhead.
- private BeforeFormat before1 = null;
- private BeforeFormat before2 = null;
- private BeforeFormat before3 = null;
- private TargetFormat target = null;
- private AfterFormat after1 = null;
- private AfterFormat after2 = null;
- private AfterFormat after3 = null;
- private final PluralRules rules;
-
- public BeforeTargetAfterFormat(PluralRules rules) {
- this.rules = rules;
- }
-
- public void addBeforeFormat(BeforeFormat before) {
- if (before1 == null) {
- before1 = before;
- } else if (before2 == null) {
- before2 = before;
- } else if (before3 == null) {
- before3 = before;
- } else {
- throw new IllegalArgumentException("Only three BeforeFormats are allowed at a time");
- }
- }
-
- public void setTargetFormat(TargetFormat target) {
- this.target = target;
- }
-
- public void addAfterFormat(AfterFormat after) {
- if (after1 == null) {
- after1 = after;
- } else if (after2 == null) {
- after2 = after;
- } else if (after3 == null) {
- after3 = after;
- } else {
- throw new IllegalArgumentException("Only three AfterFormats are allowed at a time");
- }
- }
-
- @Override
- public String format(FormatQuantity input) {
- ModifierHolder mods = threadLocalModifierHolder.get().clear();
- NumberStringBuilder sb = threadLocalStringBuilder.get().clear();
- int length = process(input, mods, sb, 0);
- length += mods.applyAll(sb, 0, length);
- return sb.toString();
- }
-
- @Override
- public int process(
- FormatQuantity input, ModifierHolder mods, NumberStringBuilder string, int startIndex) {
- // Special case: modifiers are skipped for NaN
- int length = 0;
- if (!input.isNaN()) {
- if (before1 != null) {
- before1.before(input, mods, rules);
- }
- if (before2 != null) {
- before2.before(input, mods, rules);
- }
- if (before3 != null) {
- before3.before(input, mods, rules);
- }
- }
- length = target.target(input, string, startIndex);
- length += mods.applyStrong(string, startIndex, startIndex + length);
- if (after1 != null) {
- length += after1.after(mods, string, startIndex, startIndex + length);
- }
- if (after2 != null) {
- length += after2.after(mods, string, startIndex, startIndex + length);
- }
- if (after3 != null) {
- length += after3.after(mods, string, startIndex, startIndex + length);
- }
- return length;
- }
-
- @Override
- public void export(Properties properties) {
- if (before1 != null) {
- before1.export(properties);
- }
- if (before2 != null) {
- before2.export(properties);
- }
- if (before3 != null) {
- before3.export(properties);
- }
- target.export(properties);
- if (after1 != null) {
- after1.export(properties);
- }
- if (after2 != null) {
- after2.export(properties);
- }
- if (after3 != null) {
- after3.export(properties);
- }
- }
- }
-
- public static class PositiveNegativeRounderTargetFormat extends SingularFormat {
- private final Modifier.PositiveNegativeModifier positiveNegative;
- private final Rounder rounder;
- private final TargetFormat target;
-
- public PositiveNegativeRounderTargetFormat(
- Modifier.PositiveNegativeModifier positiveNegative, Rounder rounder, TargetFormat target) {
- this.positiveNegative = positiveNegative;
- this.rounder = rounder;
- this.target = target;
- }
-
- @Override
- public String format(FormatQuantity input) {
- NumberStringBuilder sb = threadLocalStringBuilder.get().clear();
- process(input, null, sb, 0);
- return sb.toString();
- }
-
- @Override
- public int process(
- FormatQuantity input, ModifierHolder mods, NumberStringBuilder string, int startIndex) {
- // Special case: modifiers are skipped for NaN
- Modifier mod = null;
- rounder.apply(input);
- if (!input.isNaN() && positiveNegative != null) {
- mod = positiveNegative.getModifier(input.isNegative());
- }
- int length = target.target(input, string, startIndex);
- if (mod != null) {
- length += mod.apply(string, 0, length);
- }
- return length;
- }
-
- @Override
- public void export(Properties properties) {
- rounder.export(properties);
- positiveNegative.export(properties);
- target.export(properties);
- }
- }
-
- public abstract static class BeforeFormat implements Exportable {
- protected abstract void before(FormatQuantity input, ModifierHolder mods);
-
- @SuppressWarnings("unused")
- public void before(FormatQuantity input, ModifierHolder mods, PluralRules rules) {
- before(input, mods);
- }
- }
-
- public static interface TargetFormat extends Exportable {
- public abstract int target(FormatQuantity input, NumberStringBuilder string, int startIndex);
- }
-
- public static interface AfterFormat extends Exportable {
- public abstract int after(
- ModifierHolder mods, NumberStringBuilder string, int leftIndex, int rightIndex);
- }
-
- // Instead of Dequeue<BigDecimal>, it could be Deque<Quantity> where
- // we control the API of Quantity
- public abstract int process(
- Deque<FormatQuantity> inputs,
- ModifierHolder outputMods,
- NumberStringBuilder outputString,
- int startIndex);
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number;
-
-import java.math.BigDecimal;
-import java.math.MathContext;
-
-import com.ibm.icu.impl.StandardPlural;
-import com.ibm.icu.text.PluralRules;
-
-/**
- * An interface representing a number to be processed by the decimal formatting pipeline. Includes
- * methods for rounding, plural rules, and decimal digit extraction.
- *
- * <p>By design, this is NOT IMMUTABLE and NOT THREAD SAFE. It is intended to be an intermediate
- * object holding state during a pass through the decimal formatting pipeline.
- *
- * <p>Implementations of this interface are free to use any internal storage mechanism.
- *
- * <p>TODO: Should I change this to an abstract class so that logic for min/max digits doesn't need
- * to be copied to every implementation?
- */
-public interface FormatQuantity extends PluralRules.IFixedDecimal {
-
- /**
- * Sets the minimum and maximum digits that this {@link FormatQuantity} should generate. This
- * method does not perform rounding.
- *
- * @param minInt The minimum number of integer digits.
- * @param maxInt The maximum number of integer digits.
- * @param minFrac The minimum number of fraction digits.
- * @param maxFrac The maximum number of fraction digits.
- */
- public void setIntegerFractionLength(int minInt, int maxInt, int minFrac, int maxFrac);
-
- /**
- * Rounds the number to a specified interval, such as 0.05.
- *
- * <p>If rounding to a power of ten, use the more efficient {@link #roundToMagnitude} instead.
- *
- * @param roundingInterval The increment to which to round.
- * @param mathContext The {@link MathContext} to use if rounding is necessary. Undefined behavior
- * if null.
- */
- public void roundToIncrement(BigDecimal roundingInterval, MathContext mathContext);
-
- /**
- * Rounds the number to a specified magnitude (power of ten).
- *
- * @param roundingMagnitude The power of ten to which to round. For example, a value of -2 will
- * round to 2 decimal places.
- * @param mathContext The {@link MathContext} to use if rounding is necessary. Undefined behavior
- * if null.
- */
- public void roundToMagnitude(int roundingMagnitude, MathContext mathContext);
-
- /**
- * Rounds the number to an infinite number of decimal points. This has no effect except for
- * forcing the double in {@link FormatQuantityBCD} to adopt its exact representation.
- */
- public void roundToInfinity();
-
- /**
- * Multiply the internal value.
- *
- * @param multiplicand The value by which to multiply.
- */
- public void multiplyBy(BigDecimal multiplicand);
-
- /**
- * Scales the number by a power of ten. For example, if the value is currently "1234.56", calling
- * this method with delta=-3 will change the value to "1.23456".
- *
- * @param delta The number of magnitudes of ten to change by.
- */
- public void adjustMagnitude(int delta);
-
- /**
- * @return The power of ten corresponding to the most significant nonzero digit.
- * @throws ArithmeticException If the value represented is zero.
- */
- public int getMagnitude() throws ArithmeticException;
-
- /** @return Whether the value represented by this {@link FormatQuantity} is zero. */
- public boolean isZero();
-
- /** @return Whether the value represented by this {@link FormatQuantity} is less than zero. */
- public boolean isNegative();
-
- /** @return Whether the value represented by this {@link FormatQuantity} is infinite. */
- @Override
- public boolean isInfinite();
-
- /** @return Whether the value represented by this {@link FormatQuantity} is not a number. */
- @Override
- public boolean isNaN();
-
- /** @return The value contained in this {@link FormatQuantity} approximated as a double. */
- public double toDouble();
-
- public BigDecimal toBigDecimal();
-
- public int maxRepresentableDigits();
-
- // TODO: Should this method be removed, since FormatQuantity implements IFixedDecimal now?
- /**
- * Computes the plural form for this number based on the specified set of rules.
- *
- * @param rules A {@link PluralRules} object representing the set of rules.
- * @return The {@link StandardPlural} according to the PluralRules. If the plural form is not in
- * the set of standard plurals, {@link StandardPlural#OTHER} is returned instead.
- */
- public StandardPlural getStandardPlural(PluralRules rules);
-
- // /**
- // * @return The number of fraction digits, always in the closed interval [minFrac, maxFrac].
- // * @see #setIntegerFractionLength(int, int, int, int)
- // */
- // public int fractionCount();
- //
- // /**
- // * @return The number of integer digits, always in the closed interval [minInt, maxInt].
- // * @see #setIntegerFractionLength(int, int, int, int)
- // */
- // public int integerCount();
- //
- // /**
- // * @param index The index of the fraction digit relative to the decimal place, or 1 minus the
- // * digit's power of ten.
- // * @return The digit at the specified index. Undefined if index is greater than maxInt or less
- // * than 0.
- // * @see #fractionCount()
- // */
- // public byte getFractionDigit(int index);
- //
- // /**
- // * @param index The index of the integer digit relative to the decimal place, or the digit's power
- // * of ten.
- // * @return The digit at the specified index. Undefined if index is greater than maxInt or less
- // * than 0.
- // * @see #integerCount()
- // */
- // public byte getIntegerDigit(int index);
-
- /**
- * Gets the digit at the specified magnitude. For example, if the represented number is 12.3,
- * getDigit(-1) returns 3, since 3 is the digit corresponding to 10^-1.
- *
- * @param magnitude The magnitude of the digit.
- * @return The digit at the specified magnitude.
- */
- public byte getDigit(int magnitude);
-
- /**
- * Gets the largest power of ten that needs to be displayed. The value returned by this function
- * will be bounded between minInt and maxInt.
- *
- * @return The highest-magnitude digit to be displayed.
- */
- public int getUpperDisplayMagnitude();
-
- /**
- * Gets the smallest power of ten that needs to be displayed. The value returned by this function
- * will be bounded between -minFrac and -maxFrac.
- *
- * @return The lowest-magnitude digit to be displayed.
- */
- public int getLowerDisplayMagnitude();
-
- public FormatQuantity clone();
-
- public void copyFrom(FormatQuantity other);
-
- /**
- * This method is for internal testing only and should be removed before release.
- *
- * @internal
- */
- public long getPositionFingerprint();
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number;
-
-import java.math.BigDecimal;
-import java.math.MathContext;
-import java.math.RoundingMode;
-
-import com.ibm.icu.impl.StandardPlural;
-import com.ibm.icu.text.PluralRules;
-import com.ibm.icu.text.PluralRules.Operand;
-
-/**
- * This is an older implementation of FormatQuantity. A newer, faster implementation is
- * FormatQuantity2. I kept this implementation around because it was useful for testing purposes
- * (being able to compare the output of one implementation with the other).
- *
- * <p>This class is NOT IMMUTABLE and NOT THREAD SAFE and is intended to be used by a single thread
- * to format a number through a formatter, which is thread-safe.
- */
-public class FormatQuantity1 implements FormatQuantity {
- // Four positions: left optional '(', left required '[', right required ']', right optional ')'.
- // These four positions determine which digits are displayed in the output string. They do NOT
- // affect rounding. These positions are internal-only and can be specified only by the public
- // endpoints like setFractionLength, setIntegerLength, and setSignificantDigits, among others.
- //
- // * Digits between lReqPos and rReqPos are in the "required zone" and are always displayed.
- // * Digits between lOptPos and rOptPos but outside the required zone are in the "optional zone"
- // and are displayed unless they are trailing off the left or right edge of the number and
- // have a numerical value of zero. In order to be "trailing", the digits need to be beyond
- // the decimal point in their respective directions.
- // * Digits outside of the "optional zone" are never displayed.
- //
- // See the table below for illustrative examples.
- //
- // +---------+---------+---------+---------+------------+------------------------+--------------+
- // | lOptPos | lReqPos | rReqPos | rOptPos | number | positions | en-US string |
- // +---------+---------+---------+---------+------------+------------------------+--------------+
- // | 5 | 2 | -1 | -5 | 1234.567 | ( 12[34.5]67 ) | 1,234.567 |
- // | 3 | 2 | -1 | -5 | 1234.567 | 1(2[34.5]67 ) | 234.567 |
- // | 3 | 2 | -1 | -2 | 1234.567 | 1(2[34.5]6)7 | 234.56 |
- // | 6 | 4 | 2 | -5 | 123456789. | 123(45[67]89. ) | 456,789. |
- // | 6 | 4 | 2 | 1 | 123456789. | 123(45[67]8)9. | 456,780. |
- // | -1 | -1 | -3 | -4 | 0.123456 | 0.1([23]4)56 | .0234 |
- // | 6 | 4 | -2 | -2 | 12.3 | ( [ 12.3 ]) | 0012.30 |
- // +---------+---------+---------+---------+------------+------------------------+--------------+
- //
- private int lOptPos = Integer.MAX_VALUE;
- private int lReqPos = 0;
- private int rReqPos = 0;
- private int rOptPos = Integer.MIN_VALUE;
-
- // Internally, attempt to use a long to store the number. A long can hold numbers between 18 and
- // 19 digits, covering the vast majority of use cases. We store three values: the long itself,
- // the "scale" of the long (the power of 10 represented by the rightmost digit in the long), and
- // the "precision" (the number of digits in the long). "primary" and "primaryScale" are the only
- // two variables that are required for representing the number in memory. "primaryPrecision" is
- // saved only for the sake of performance enhancements when performing certain operations. It can
- // always be re-computed from "primary" and "primaryScale".
- private long primary;
- private int primaryScale;
- private int primaryPrecision;
-
- // If the decimal can't fit into the long, fall back to a BigDecimal.
- private BigDecimal fallback;
-
- // Other properties
- private int flags;
- private static final int NEGATIVE_FLAG = 1;
- private static final int INFINITY_FLAG = 2;
- private static final int NAN_FLAG = 4;
- private static final long[] POWERS_OF_TEN = {
- 1L,
- 10L,
- 100L,
- 1000L,
- 10000L,
- 100000L,
- 1000000L,
- 10000000L,
- 100000000L,
- 1000000000L,
- 10000000000L,
- 100000000000L,
- 1000000000000L,
- 10000000000000L,
- 100000000000000L,
- 1000000000000000L,
- 10000000000000000L,
- 100000000000000000L,
- 1000000000000000000L
- };
-
- @Override
- public int maxRepresentableDigits() {
- return Integer.MAX_VALUE;
- }
-
- public FormatQuantity1(long input) {
- if (input < 0) {
- setNegative(true);
- input *= -1;
- }
-
- primary = input;
- primaryScale = 0;
- primaryPrecision = computePrecision(primary);
- fallback = null;
- }
-
- /**
- * Creates a FormatQuantity from the given double value. Internally attempts several strategies
- * for converting the double to an exact representation, falling back on a BigDecimal if it fails
- * to do so.
- *
- * @param input The double to represent by this FormatQuantity.
- */
- public FormatQuantity1(double input) {
- if (input < 0) {
- setNegative(true);
- input *= -1;
- }
-
- // First try reading from IEEE bits. This is trivial only for doubles in [2^52, 2^64). If it
- // fails, we wasted only a few CPU cycles.
- long ieeeBits = Double.doubleToLongBits(input);
- int exponent = (int) ((ieeeBits & 0x7ff0000000000000L) >> 52) - 0x3ff;
- if (exponent >= 52 && exponent <= 63) {
- // We can convert this double directly to a long.
- long mantissa = (ieeeBits & 0x000fffffffffffffL) + 0x0010000000000000L;
- primary = (mantissa << (exponent - 52));
- primaryScale = 0;
- primaryPrecision = computePrecision(primary);
- return;
- }
-
- // Now try parsing the string produced by Double.toString().
- String temp = Double.toString(input);
- try {
- if (temp.length() == 3 && temp.equals("0.0")) {
- // Case 1: Zero.
- primary = 0L;
- primaryScale = 0;
- primaryPrecision = 0;
- } else if (temp.indexOf('E') != -1) {
- // Case 2: Exponential notation.
- assert temp.indexOf('.') == 1;
- int expPos = temp.indexOf('E');
- primary = Long.parseLong(temp.charAt(0) + temp.substring(2, expPos));
- primaryScale = Integer.parseInt(temp.substring(expPos + 1)) - (expPos - 1) + 1;
- primaryPrecision = expPos - 1;
- } else if (temp.charAt(0) == '0') {
- // Case 3: Fraction-only number.
- assert temp.indexOf('.') == 1;
- primary = Long.parseLong(temp.substring(2)); // ignores leading zeros
- primaryScale = 2 - temp.length();
- primaryPrecision = computePrecision(primary);
- } else if (temp.charAt(temp.length() - 1) == '0') {
- // Case 4: Integer-only number.
- assert temp.indexOf('.') == temp.length() - 2;
- int rightmostNonzeroDigitIndex = temp.length() - 3;
- while (temp.charAt(rightmostNonzeroDigitIndex) == '0') {
- rightmostNonzeroDigitIndex -= 1;
- }
- primary = Long.parseLong(temp.substring(0, rightmostNonzeroDigitIndex + 1));
- primaryScale = temp.length() - rightmostNonzeroDigitIndex - 3;
- primaryPrecision = rightmostNonzeroDigitIndex + 1;
- } else if (temp.equals("Infinity")) {
- // Case 5: Infinity.
- primary = 0;
- setInfinity(true);
- } else if (temp.equals("NaN")) {
- // Case 6: NaN.
- primary = 0;
- setNaN(true);
- } else {
- // Case 7: Number with both a fraction and an integer.
- int decimalPos = temp.indexOf('.');
- primary = Long.parseLong(temp.substring(0, decimalPos) + temp.substring(decimalPos + 1));
- primaryScale = decimalPos - temp.length() + 1;
- primaryPrecision = temp.length() - 1;
- }
- } catch (NumberFormatException e) {
- // The digits of the double can't fit into the long.
- primary = -1;
- fallback = new BigDecimal(temp);
- }
- }
-
- static final double LOG_2_OF_TEN = 3.32192809489;
-
- public FormatQuantity1(double input, boolean fast) {
- if (input < 0) {
- setNegative(true);
- input *= -1;
- }
-
- // Our strategy is to read all digits that are *guaranteed* to be valid without delving into
- // the IEEE rounding rules. This strategy might not end up with a perfect representation of
- // the fractional part of the double.
- long ieeeBits = Double.doubleToLongBits(input);
- int exponent = (int) ((ieeeBits & 0x7ff0000000000000L) >> 52) - 0x3ff;
- long mantissa = (ieeeBits & 0x000fffffffffffffL) + 0x0010000000000000L;
- if (exponent > 63) {
- throw new IllegalArgumentException(); // FIXME
- } else if (exponent >= 52) {
- primary = (mantissa << (exponent - 52));
- primaryScale = 0;
- primaryPrecision = computePrecision(primary);
- return;
- } else if (exponent >= 0) {
- int shift = 52 - exponent;
- primary = (mantissa >> shift); // integer part
- int fractionCount = (int) (shift / LOG_2_OF_TEN);
- long fraction = (mantissa - (primary << shift)) + 1L; // TODO: Explain the +1L
- primary *= POWERS_OF_TEN[fractionCount];
- for (int i = 0; i < fractionCount; i++) {
- long times10 = (fraction * 10L);
- long digit = times10 >> shift;
- assert digit >= 0 && digit < 10;
- primary += digit * POWERS_OF_TEN[fractionCount - i - 1];
- fraction = times10 & ((1L << shift) - 1);
- }
- primaryScale = -fractionCount;
- primaryPrecision = computePrecision(primary);
- } else {
- throw new IllegalArgumentException(); // FIXME
- }
- }
-
- public FormatQuantity1(BigDecimal decimal) {
- if (decimal.compareTo(BigDecimal.ZERO) < 0) {
- setNegative(true);
- decimal = decimal.negate();
- }
-
- primary = -1;
- if (decimal.compareTo(BigDecimal.ZERO) == 0) {
- fallback = BigDecimal.ZERO;
- } else {
- fallback = decimal;
- }
- }
-
- public FormatQuantity1(FormatQuantity1 other) {
- copyFrom(other);
- }
-
- @Override
- public FormatQuantity clone() {
- return new FormatQuantity1(this);
- }
-
- /**
- * Make the internal state of this FormatQuantity equal to another FormatQuantity.
- *
- * @param other The template FormatQuantity. All properties from this FormatQuantity will be
- * copied into this FormatQuantity.
- */
- @Override
- public void copyFrom(FormatQuantity other) {
- // TODO: Check before casting
- FormatQuantity1 _other = (FormatQuantity1) other;
- lOptPos = _other.lOptPos;
- lReqPos = _other.lReqPos;
- rReqPos = _other.rReqPos;
- rOptPos = _other.rOptPos;
- primary = _other.primary;
- primaryScale = _other.primaryScale;
- primaryPrecision = _other.primaryPrecision;
- fallback = _other.fallback;
- flags = _other.flags;
- }
-
- @Override
- public long getPositionFingerprint() {
- long fingerprint = 0;
- fingerprint ^= lOptPos;
- fingerprint ^= (lReqPos << 16);
- fingerprint ^= ((long) rReqPos << 32);
- fingerprint ^= ((long) rOptPos << 48);
- return fingerprint;
- }
-
- /**
- * Utility method to compute the number of digits ("precision") in a long.
- *
- * @param input The long (which can't contain more than 19 digits).
- * @return The precision of the long.
- */
- private static int computePrecision(long input) {
- int precision = 0;
- while (input > 0) {
- input /= 10;
- precision++;
- }
- return precision;
- }
-
- /**
- * Changes the internal representation from a long to a BigDecimal. Used only for operations that
- * don't support longs.
- */
- private void convertToBigDecimal() {
- if (primary == -1) {
- return;
- }
-
- fallback = new BigDecimal(primary).scaleByPowerOfTen(primaryScale);
- primary = -1;
- }
-
- @Override
- public void setIntegerFractionLength(int minInt, int maxInt, int minFrac, int maxFrac) {
- // Graceful failures for bogus input
- minInt = Math.max(0, minInt);
- maxInt = Math.max(0, maxInt);
- minFrac = Math.max(0, minFrac);
- maxFrac = Math.max(0, maxFrac);
-
- // The minima must be less than or equal to the maxima
- if (maxInt < minInt) {
- minInt = maxInt;
- }
- if (maxFrac < minFrac) {
- minFrac = maxFrac;
- }
-
- // Displaying neither integer nor fraction digits is not allowed
- if (maxInt == 0 && maxFrac == 0) {
- maxInt = Integer.MAX_VALUE;
- maxFrac = Integer.MAX_VALUE;
- }
-
- // Save values into internal state
- // Negation is safe for minFrac/maxFrac because -Integer.MAX_VALUE > Integer.MIN_VALUE
- lOptPos = maxInt;
- lReqPos = minInt;
- rReqPos = -minFrac;
- rOptPos = -maxFrac;
- }
-
- @Override
- public void roundToIncrement(BigDecimal roundingInterval, MathContext mathContext) {
- BigDecimal d =
- (primary == -1) ? fallback : new BigDecimal(primary).scaleByPowerOfTen(primaryScale);
- if (isNegative()) d = d.negate();
- d = d.divide(roundingInterval, 0, mathContext.getRoundingMode()).multiply(roundingInterval);
- if (isNegative()) d = d.negate();
- fallback = d;
- primary = -1;
- }
-
- @Override
- public void roundToMagnitude(int roundingMagnitude, MathContext mathContext) {
- if (roundingMagnitude < -1000) {
- roundToInfinity();
- return;
- }
- if (primary == -1) {
- if (isNegative()) fallback = fallback.negate();
- fallback = fallback.setScale(-roundingMagnitude, mathContext.getRoundingMode());
- if (isNegative()) fallback = fallback.negate();
- // Enforce the math context.
- fallback = fallback.round(mathContext);
- } else {
- int relativeScale = primaryScale - roundingMagnitude;
- if (relativeScale < -18) {
- // No digits will remain after rounding the number.
- primary = 0L;
- primaryScale = roundingMagnitude;
- primaryPrecision = 0;
- } else if (relativeScale < 0) {
- // This is the harder case, when we need to perform the rounding logic.
- // First check if the rightmost digits are already zero, where we can skip rounding.
- if ((primary % POWERS_OF_TEN[0 - relativeScale]) == 0) {
- // No rounding is necessary.
- } else {
- // TODO: Make this more efficient. Temporarily, convert to a BigDecimal and back again.
- BigDecimal temp = new BigDecimal(primary).scaleByPowerOfTen(primaryScale);
- if (isNegative()) temp = temp.negate();
- temp = temp.setScale(-roundingMagnitude, mathContext.getRoundingMode());
- if (isNegative()) temp = temp.negate();
- temp = temp.scaleByPowerOfTen(-roundingMagnitude);
- primary = temp.longValueExact(); // should never throw
- primaryScale = roundingMagnitude;
- primaryPrecision = computePrecision(primary);
- }
- } else {
- // No rounding is necessary. All digits are to the left of the rounding magnitude.
- }
- // Enforce the math context.
- primary = new BigDecimal(primary).round(mathContext).longValueExact();
- primaryPrecision = computePrecision(primary);
- }
- }
-
- @Override
- public void roundToInfinity() {
- // noop
- }
-
- /**
- * Multiply the internal number by the specified multiplicand. This method forces the internal
- * representation into a BigDecimal. If you are multiplying by a power of 10, use {@link
- * #adjustMagnitude} instead.
- *
- * @param multiplicand The number to be passed to {@link BigDecimal#multiply}.
- */
- @Override
- public void multiplyBy(BigDecimal multiplicand) {
- convertToBigDecimal();
- fallback = fallback.multiply(multiplicand);
- if (fallback.compareTo(BigDecimal.ZERO) < 0) {
- setNegative(!isNegative());
- fallback = fallback.negate();
- }
- }
-
- /**
- * Divide the internal number by the specified quotient. This method forces the internal
- * representation into a BigDecimal. If you are dividing by a power of 10, use {@link
- * #adjustMagnitude} instead.
- *
- * @param divisor The number to be passed to {@link BigDecimal#divide}.
- * @param scale The scale of the final rounded number. More negative means more decimal places.
- * @param mathContext The math context to use if rounding is necessary.
- */
- private void divideBy(BigDecimal divisor, int scale, MathContext mathContext) {
- convertToBigDecimal();
- // Negate the scale because BigDecimal's scale is defined as the inverse of our scale
- fallback = fallback.divide(divisor, -scale, mathContext.getRoundingMode());
- if (fallback.compareTo(BigDecimal.ZERO) < 0) {
- setNegative(!isNegative());
- fallback = fallback.negate();
- }
- }
-
- @Override
- public boolean isZero() {
- if (primary == -1) {
- return fallback.compareTo(BigDecimal.ZERO) == 0;
- } else {
- return primary == 0;
- }
- }
-
- /** @return The power of ten of the highest digit represented by this FormatQuantity */
- @Override
- public int getMagnitude() throws ArithmeticException {
- int scale = (primary == -1) ? scaleBigDecimal(fallback) : primaryScale;
- int precision = (primary == -1) ? precisionBigDecimal(fallback) : primaryPrecision;
- if (precision == 0) {
- throw new ArithmeticException("Magnitude is not well-defined for zero");
- } else {
- return scale + precision - 1;
- }
- }
-
- /**
- * Changes the magnitude of this FormatQuantity. If the indices of the represented digits had been
- * previously specified, those indices are moved relative to the FormatQuantity.
- *
- * <p>This method does NOT perform rounding.
- *
- * @param delta The number of powers of ten to shift (positive shifts to the left).
- */
- @Override
- public void adjustMagnitude(int delta) {
- if (primary == -1) {
- fallback = fallback.scaleByPowerOfTen(delta);
- } else {
- primaryScale = addOrMaxValue(primaryScale, delta);
- }
- }
-
- private static int addOrMaxValue(int a, int b) {
- // Check for overflow, and return min/max value if overflow occurs.
- if (b < 0 && a + b > a) {
- return Integer.MIN_VALUE;
- } else if (b > 0 && a + b < a) {
- return Integer.MAX_VALUE;
- }
- return a + b;
- }
-
- /** @return If the number represented by this FormatQuantity is less than zero */
- @Override
- public boolean isNegative() {
- return (flags & NEGATIVE_FLAG) != 0;
- }
-
- private void setNegative(boolean isNegative) {
- flags = (flags & (~NEGATIVE_FLAG)) | (isNegative ? NEGATIVE_FLAG : 0);
- }
-
- @Override
- public boolean isInfinite() {
- return (flags & INFINITY_FLAG) != 0;
- }
-
- private void setInfinity(boolean isInfinity) {
- flags = (flags & (~INFINITY_FLAG)) | (isInfinity ? INFINITY_FLAG : 0);
- }
-
- @Override
- public boolean isNaN() {
- return (flags & NAN_FLAG) != 0;
- }
-
- private void setNaN(boolean isNaN) {
- flags = (flags & (~NAN_FLAG)) | (isNaN ? NAN_FLAG : 0);
- }
-
- /**
- * Returns a representation of this FormatQuantity as a double, with possible loss of information.
- */
- @Override
- public double toDouble() {
- double result;
- if (primary == -1) {
- result = fallback.doubleValue();
- } else {
- // TODO: Make this more efficient
- result = primary;
- for (int i = 0; i < primaryScale; i++) {
- result *= 10.;
- }
- for (int i = 0; i > primaryScale; i--) {
- result /= 10.;
- }
- }
- return isNegative() ? -result : result;
- }
-
- @Override
- public BigDecimal toBigDecimal() {
- BigDecimal result;
- if (primary != -1) {
- result = new BigDecimal(primary).scaleByPowerOfTen(primaryScale);
- } else {
- result = fallback;
- }
- return isNegative() ? result.negate() : result;
- }
-
- /** @return */
- @Override
- public StandardPlural getStandardPlural(PluralRules rules) {
- if (rules == null) {
- // Fail gracefully if the user didn't provide a PluralRules
- return StandardPlural.OTHER;
- } else {
- // TODO: Avoid converting to a double for the sake of PluralRules
- String ruleString = rules.select(toDouble());
- return StandardPlural.orOtherFromString(ruleString);
- }
- }
-
- @Override
- public double getPluralOperand(Operand operand) {
- // TODO: This is a temporary hack.
- return new PluralRules.FixedDecimal(toDouble()).getPluralOperand(operand);
- }
-
- /** @return */
- public boolean hasNextFraction() {
- if (rReqPos < 0) {
- // We are in the required zone.
- return true;
- } else if (rOptPos >= 0) {
- // We are in the forbidden zone.
- return false;
- } else {
- // We are in the optional zone.
- if (primary == -1) {
- return fallback.remainder(BigDecimal.ONE).compareTo(BigDecimal.ZERO) > 0;
- } else {
- if (primaryScale <= -19) {
- // The number is a fraction so small that it consists of only fraction digits.
- return primary > 0;
- } else if (primaryScale < 0) {
- // Check if we have a fraction part.
- long factor = POWERS_OF_TEN[0 - primaryScale];
- return ((primary % factor) != 0);
- } else {
- // The lowest digit in the long has magnitude greater than -1.
- return false;
- }
- }
- }
- }
-
- /** @return */
- public byte nextFraction() {
- byte returnValue;
- if (primary == -1) {
- BigDecimal temp = fallback.multiply(BigDecimal.TEN);
- returnValue = temp.setScale(0, RoundingMode.FLOOR).remainder(BigDecimal.TEN).byteValue();
- fallback = fallback.setScale(0, RoundingMode.FLOOR).add(temp.remainder(BigDecimal.ONE));
- } else {
- if (primaryScale <= -20) {
- // The number is a fraction so small that it has no first fraction digit.
- primaryScale += 1;
- returnValue = 0;
- } else if (primaryScale < 0) {
- // Extract the fraction digit out of the middle of the long.
- long factor = POWERS_OF_TEN[0 - primaryScale - 1];
- long temp1 = primary / factor;
- long temp2 = primary % factor;
- returnValue = (byte) (temp1 % 10); // not necessarily nonzero
- primary = ((temp1 / 10) * factor) + temp2;
- primaryScale += 1;
- if (temp1 != 0) {
- primaryPrecision -= 1;
- }
- } else {
- // The lowest digit in the long has magnitude greater than -1.
- returnValue = 0;
- }
- }
-
- // Update digit brackets
- if (lOptPos < 0) {
- lOptPos += 1;
- }
- if (lReqPos < 0) {
- lReqPos += 1;
- }
- if (rReqPos < 0) {
- rReqPos += 1;
- }
- if (rOptPos < 0) {
- rOptPos += 1;
- }
-
- assert returnValue >= 0;
- return returnValue;
- }
-
- /** @return */
- public boolean hasNextInteger() {
- if (lReqPos > 0) {
- // We are in the required zone.
- return true;
- } else if (lOptPos <= 0) {
- // We are in the forbidden zone.
- return false;
- } else {
- // We are in the optional zone.
- if (primary == -1) {
- return fallback.setScale(0, RoundingMode.FLOOR).compareTo(BigDecimal.ZERO) > 0;
- } else {
- if (primaryScale < -18) {
- // The number is a fraction so small that it has no integer part.
- return false;
- } else if (primaryScale < 0) {
- // Check if we have an integer part.
- long factor = POWERS_OF_TEN[0 - primaryScale];
- return ((primary % factor) != primary); // equivalent: ((primary / 10) != 0)
- } else {
- // The lowest digit in the long has magnitude of at least 0.
- return primary != 0;
- }
- }
- }
- }
-
- private int integerCount() {
- int digitsRemaining;
- if (primary == -1) {
- digitsRemaining = precisionBigDecimal(fallback) + scaleBigDecimal(fallback);
- } else {
- digitsRemaining = primaryPrecision + primaryScale;
- }
- return Math.min(Math.max(digitsRemaining, lReqPos), lOptPos);
- }
-
- private int fractionCount() {
- // TODO: This is temporary.
- FormatQuantity1 copy = (FormatQuantity1) this.clone();
- int fractionCount = 0;
- while (copy.hasNextFraction()) {
- copy.nextFraction();
- fractionCount++;
- }
- return fractionCount;
- }
-
- @Override
- public int getUpperDisplayMagnitude() {
- return integerCount() - 1;
- }
-
- @Override
- public int getLowerDisplayMagnitude() {
- return -fractionCount();
- }
-
- // @Override
- // public byte getIntegerDigit(int index) {
- // return getDigitPos(index);
- // }
- //
- // @Override
- // public byte getFractionDigit(int index) {
- // return getDigitPos(-index - 1);
- // }
-
- @Override
- public byte getDigit(int magnitude) {
- // TODO: This is temporary.
- FormatQuantity1 copy = (FormatQuantity1) this.clone();
- if (magnitude < 0) {
- for (int p = -1; p > magnitude; p--) {
- copy.nextFraction();
- }
- return copy.nextFraction();
- } else {
- for (int p = 0; p < magnitude; p++) {
- copy.nextInteger();
- }
- return copy.nextInteger();
- }
- }
-
- /** @return */
- public byte nextInteger() {
- byte returnValue;
- if (primary == -1) {
- returnValue = fallback.setScale(0, RoundingMode.FLOOR).remainder(BigDecimal.TEN).byteValue();
- BigDecimal temp = fallback.divide(BigDecimal.TEN).setScale(0, RoundingMode.FLOOR);
- fallback = fallback.remainder(BigDecimal.ONE).add(temp);
- } else {
- if (primaryScale < -18) {
- // The number is a fraction so small that it has no integer part.
- returnValue = 0;
- } else if (primaryScale < 0) {
- // Extract the integer digit out of the middle of the long. In many ways, this is the heart
- // of the digit iterator algorithm.
- long factor = POWERS_OF_TEN[0 - primaryScale];
- if ((primary % factor) != primary) { // equivalent: ((primary / 10) != 0)
- returnValue = (byte) ((primary / factor) % 10);
- long temp = (primary / 10);
- primary = temp - (temp % factor) + (primary % factor);
- primaryPrecision -= 1;
- } else {
- returnValue = 0;
- }
- } else if (primaryScale == 0) {
- // Fast-path for primaryScale == 0 (otherwise equivalent to previous step).
- if (primary != 0) {
- returnValue = (byte) (primary % 10);
- primary /= 10;
- primaryPrecision -= 1;
- } else {
- returnValue = 0;
- }
- } else {
- // The lowest digit in the long has magnitude greater than 0.
- primaryScale -= 1;
- returnValue = 0;
- }
- }
-
- // Update digit brackets
- if (lOptPos > 0) {
- lOptPos -= 1;
- }
- if (lReqPos > 0) {
- lReqPos -= 1;
- }
- if (rReqPos > 0) {
- rReqPos -= 1;
- }
- if (rOptPos > 0) {
- rOptPos -= 1;
- }
-
- assert returnValue >= 0;
- return returnValue;
- }
-
- /**
- * Helper method to compute the precision of a BigDecimal by our definition of precision, which is
- * that the number zero gets precision zero.
- *
- * @param decimal The BigDecimal whose precision to compute.
- * @return The precision by our definition.
- */
- private static int precisionBigDecimal(BigDecimal decimal) {
- if (decimal.compareTo(BigDecimal.ZERO) == 0) {
- return 0;
- } else {
- return decimal.precision();
- }
- }
-
- /**
- * Helper method to compute the scale of a BigDecimal by our definition of scale, which is that
- * deeper fractions result in negative scales as opposed to positive scales.
- *
- * @param decimal The BigDecimal whose scale to compute.
- * @return The scale by our definition.
- */
- private static int scaleBigDecimal(BigDecimal decimal) {
- return -decimal.scale();
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append("<FormatQuantity1 ");
- if (primary == -1) {
- sb.append(lOptPos > 1000 ? "max" : lOptPos);
- sb.append(":");
- sb.append(lReqPos);
- sb.append(":");
- sb.append(rReqPos);
- sb.append(":");
- sb.append(rOptPos < -1000 ? "min" : rOptPos);
- sb.append(" ");
- sb.append(fallback.toString());
- } else {
- String digits = Long.toString(primary);
- int iDec = digits.length() + primaryScale;
- int iLP = iDec - toRange(lOptPos, -1000, 1000);
- int iLB = iDec - toRange(lReqPos, -1000, 1000);
- int iRB = iDec - toRange(rReqPos, -1000, 1000);
- int iRP = iDec - toRange(rOptPos, -1000, 1000);
- iDec = Math.max(Math.min(iDec, digits.length() + 1), -1);
- iLP = Math.max(Math.min(iLP, digits.length() + 1), -1);
- iLB = Math.max(Math.min(iLB, digits.length() + 1), -1);
- iRB = Math.max(Math.min(iRB, digits.length() + 1), -1);
- iRP = Math.max(Math.min(iRP, digits.length() + 1), -1);
-
- for (int i = -1; i <= digits.length() + 1; i++) {
- if (i == iLP) sb.append('(');
- if (i == iLB) sb.append('[');
- if (i == iDec) sb.append('.');
- if (i == iRB) sb.append(']');
- if (i == iRP) sb.append(')');
- if (i >= 0 && i < digits.length()) sb.append(digits.charAt(i));
- else sb.append('\u00A0');
- }
- }
- sb.append(">");
- return sb.toString();
- }
-
- private static int toRange(int i, int lo, int hi) {
- if (i < lo) {
- return lo;
- } else if (i > hi) {
- return hi;
- } else {
- return i;
- }
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number;
-
-import java.math.BigDecimal;
-import java.math.BigInteger;
-
-public final class FormatQuantity2 extends FormatQuantityBCD {
-
- /**
- * The BCD of the 16 digits of the number represented by this object. Every 4 bits of the long map
- * to one digit. For example, the number "12345" in BCD is "0x12345".
- *
- * <p>Whenever bcd changes internally, {@link #compact()} must be called, except in special cases
- * like setting the digit to zero.
- */
- private long bcd;
-
- @Override
- public int maxRepresentableDigits() {
- return 16;
- }
-
- public FormatQuantity2(long input) {
- setToLong(input);
- }
-
- public FormatQuantity2(int input) {
- setToInt(input);
- }
-
- public FormatQuantity2(double input) {
- setToDouble(input);
- }
-
- public FormatQuantity2(BigInteger input) {
- setToBigInteger(input);
- }
-
- public FormatQuantity2(BigDecimal input) {
- setToBigDecimal(input);
- }
-
- public FormatQuantity2(FormatQuantity2 other) {
- copyFrom(other);
- }
-
- @Override
- protected byte getDigitPos(int position) {
- if (position < 0 || position >= 16) return 0;
- return (byte) ((bcd >>> (position * 4)) & 0xf);
- }
-
- @Override
- protected void setDigitPos(int position, byte value) {
- assert position >= 0 && position < 16;
- int shift = position * 4;
- bcd = bcd & ~(0xfL << shift) | ((long) value << shift);
- }
-
- @Override
- protected void shiftLeft(int numDigits) {
- assert precision + numDigits <= 16;
- bcd <<= (numDigits * 4);
- scale -= numDigits;
- precision += numDigits;
- }
-
- @Override
- protected void shiftRight(int numDigits) {
- bcd >>>= (numDigits * 4);
- scale += numDigits;
- precision -= numDigits;
- }
-
- @Override
- protected void setBcdToZero() {
- bcd = 0L;
- scale = 0;
- precision = 0;
- isApproximate = false;
- origDouble = 0;
- origDelta = 0;
- }
-
- @Override
- protected void readIntToBcd(int n) {
- long result = 0L;
- int i = 16;
- for (; n != 0; n /= 10, i--) {
- result = (result >>> 4) + (((long) n % 10) << 60);
- }
- // ints can't overflow the 16 digits in the BCD, so scale is always zero
- bcd = result >>> (i * 4);
- scale = 0;
- precision = 16 - i;
- }
-
- @Override
- protected void readLongToBcd(long n) {
- long result = 0L;
- int i = 16;
- for (; n != 0L; n /= 10L, i--) {
- result = (result >>> 4) + ((n % 10) << 60);
- }
- int adjustment = (i > 0) ? i : 0;
- bcd = result >>> (adjustment * 4);
- scale = (i < 0) ? -i : 0;
- precision = 16 - i;
- }
-
- @Override
- protected void readBigIntegerToBcd(BigInteger n) {
- long result = 0L;
- int i = 16;
- for (; n.signum() != 0; i--) {
- BigInteger[] temp = n.divideAndRemainder(BigInteger.TEN);
- result = (result >>> 4) + (temp[1].longValue() << 60);
- n = temp[0];
- }
- int adjustment = (i > 0) ? i : 0;
- bcd = result >>> (adjustment * 4);
- scale = (i < 0) ? -i : 0;
- }
-
- @Override
- protected BigDecimal bcdToBigDecimal() {
- long tempLong = 0L;
- for (int shift = (precision - 1); shift >= 0; shift--) {
- tempLong = tempLong * 10 + getDigitPos(shift);
- }
- BigDecimal result = BigDecimal.valueOf(tempLong);
- result = result.scaleByPowerOfTen(scale);
- if (isNegative()) result = result.negate();
- return result;
- }
-
- @Override
- protected void compact() {
- // Special handling for 0
- if (bcd == 0L) {
- scale = 0;
- precision = 0;
- return;
- }
-
- // Compact the number (remove trailing zeros)
- int delta = Long.numberOfTrailingZeros(bcd) / 4;
- bcd >>>= delta * 4;
- scale += delta;
-
- // Compute precision
- precision = 16 - (Long.numberOfLeadingZeros(bcd) / 4);
- }
-
- @Override
- protected void copyBcdFrom(FormatQuantity _other) {
- FormatQuantity2 other = (FormatQuantity2) _other;
- bcd = other.bcd;
- }
-
- @Override
- public String toString() {
- return String.format(
- "<FormatQuantity2 %s:%d:%d:%s %016XE%d>",
- (lOptPos > 1000 ? "max" : String.valueOf(lOptPos)),
- lReqPos,
- rReqPos,
- (rOptPos < -1000 ? "min" : String.valueOf(rOptPos)),
- bcd,
- scale);
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number;
-
-import java.math.BigDecimal;
-import java.math.BigInteger;
-
-public final class FormatQuantity3 extends FormatQuantityBCD {
-
- /**
- * The BCD of the 16 digits of the number represented by this object. Every 4 bits of the long map
- * to one digit. For example, the number "12345" in BCD is "0x12345".
- *
- * <p>Whenever bcd changes internally, {@link #compact()} must be called, except in special cases
- * like setting the digit to zero.
- */
- private byte[] bcd = new byte[100];
-
- @Override
- public int maxRepresentableDigits() {
- return Integer.MAX_VALUE;
- }
-
- public FormatQuantity3(long input) {
- setToLong(input);
- }
-
- public FormatQuantity3(int input) {
- setToInt(input);
- }
-
- public FormatQuantity3(double input) {
- setToDouble(input);
- }
-
- public FormatQuantity3(BigInteger input) {
- setToBigInteger(input);
- }
-
- public FormatQuantity3(BigDecimal input) {
- setToBigDecimal(input);
- }
-
- public FormatQuantity3(FormatQuantity3 other) {
- copyFrom(other);
- }
-
- @Override
- protected byte getDigitPos(int position) {
- if (position < 0 || position > precision) return 0;
- return bcd[position];
- }
-
- @Override
- protected void setDigitPos(int position, byte value) {
- assert position >= 0;
- ensureCapacity(position + 1);
- bcd[position] = value;
- }
-
- @Override
- protected void shiftLeft(int numDigits) {
- ensureCapacity(precision + numDigits);
- int i = precision + numDigits - 1;
- for (; i >= numDigits; i--) {
- bcd[i] = bcd[i - numDigits];
- }
- for (; i >= 0; i--) {
- bcd[i] = 0;
- }
- scale -= numDigits;
- precision += numDigits;
- }
-
- @Override
- protected void shiftRight(int numDigits) {
- int i = 0;
- for (; i < precision - numDigits; i++) {
- bcd[i] = bcd[i + numDigits];
- }
- for (; i < precision; i++) {
- bcd[i] = 0;
- }
- scale += numDigits;
- precision -= numDigits;
- }
-
- @Override
- protected void setBcdToZero() {
- for (int i = 0; i < precision; i++) {
- bcd[i] = (byte) 0;
- }
- scale = 0;
- precision = 0;
- isApproximate = false;
- origDouble = 0;
- origDelta = 0;
- }
-
- @Override
- protected void readIntToBcd(int n) {
- int i = 0;
- for (; n != 0L; n /= 10L, i++) {
- bcd[i] = (byte) (n % 10);
- }
- scale = 0;
- precision = i;
- }
-
- private static final byte[] LONG_MIN_VALUE =
- new byte[] {8, 0, 8, 5, 7, 7, 4, 5, 8, 6, 3, 0, 2, 7, 3, 3, 2, 2, 9};
-
- @Override
- protected void readLongToBcd(long n) {
- if (n == Long.MIN_VALUE) {
- // Can't consume via the normal path.
- System.arraycopy(LONG_MIN_VALUE, 0, bcd, 0, LONG_MIN_VALUE.length);
- scale = 0;
- precision = LONG_MIN_VALUE.length;
- return;
- }
- int i = 0;
- for (; n != 0L; n /= 10L, i++) {
- bcd[i] = (byte) (n % 10);
- }
- scale = 0;
- precision = i;
- }
-
- @Override
- protected void readBigIntegerToBcd(BigInteger n) {
- int i = 0;
- for (; n.signum() != 0; i++) {
- BigInteger[] temp = n.divideAndRemainder(BigInteger.TEN);
- ensureCapacity(i + 1);
- bcd[i] = temp[1].byteValue();
- n = temp[0];
- }
- scale = 0;
- precision = i;
- }
-
- @Override
- protected BigDecimal bcdToBigDecimal() {
- // Converting to a string here is faster than doing BigInteger/BigDecimal arithmetic.
- return new BigDecimal(toDumbString());
- }
-
- private String toDumbString() {
- StringBuilder sb = new StringBuilder();
- if (isNegative()) sb.append('-');
- if (precision == 0) {
- sb.append('0');
- return sb.toString();
- }
- for (int i = precision - 1; i >= 0; i--) {
- sb.append(getDigitPos(i));
- }
- if (scale != 0) {
- sb.append('E');
- sb.append(scale);
- }
- return sb.toString();
- }
-
- @Override
- protected void compact() {
- // Special handling for 0
- boolean isZero = true;
- for (int i = 0; i < precision; i++) {
- if (bcd[i] != 0) {
- isZero = false;
- break;
- }
- }
- if (isZero) {
- scale = 0;
- precision = 0;
- return;
- }
-
- // Compact the number (remove trailing zeros)
- int delta = 0;
- for (; bcd[delta] == 0; delta++) ;
- shiftRight(delta);
-
- // Compute precision
- int leading = precision - 1;
- for (; leading >= 0 && bcd[leading] == 0; leading--) ;
- precision = leading + 1;
- }
-
- private void ensureCapacity(int capacity) {
- if (bcd.length >= capacity) return;
- byte[] bcd1 = new byte[capacity * 2];
- System.arraycopy(bcd, 0, bcd1, 0, bcd.length);
- bcd = bcd1;
- }
-
- @Override
- protected void copyBcdFrom(FormatQuantity _other) {
- FormatQuantity3 other = (FormatQuantity3) _other;
- System.arraycopy(other.bcd, 0, bcd, 0, bcd.length);
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- for (int i = 30; i >= 0; i--) {
- sb.append(bcd[i]);
- }
- return String.format(
- "<FormatQuantity3 %s:%d:%d:%s %s%s%d>",
- (lOptPos > 1000 ? "max" : String.valueOf(lOptPos)),
- lReqPos,
- rReqPos,
- (rOptPos < -1000 ? "min" : String.valueOf(rOptPos)),
- sb,
- "E",
- scale);
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number;
-
-import java.math.BigDecimal;
-import java.math.BigInteger;
-
-public final class FormatQuantity4 extends FormatQuantityBCD {
-
- /**
- * The BCD of the 16 digits of the number represented by this object. Every 4 bits of the long map
- * to one digit. For example, the number "12345" in BCD is "0x12345".
- *
- * <p>Whenever bcd changes internally, {@link #compact()} must be called, except in special cases
- * like setting the digit to zero.
- */
- private byte[] bcdBytes;
-
- private long bcdLong = 0L;
-
- private boolean usingBytes = false;;
-
- @Override
- public int maxRepresentableDigits() {
- return Integer.MAX_VALUE;
- }
-
- public FormatQuantity4() {
- setBcdToZero();
- }
-
- public FormatQuantity4(long input) {
- setToLong(input);
- }
-
- public FormatQuantity4(int input) {
- setToInt(input);
- }
-
- public FormatQuantity4(double input) {
- setToDouble(input);
- }
-
- public FormatQuantity4(BigInteger input) {
- setToBigInteger(input);
- }
-
- public FormatQuantity4(BigDecimal input) {
- setToBigDecimal(input);
- }
-
- public FormatQuantity4(FormatQuantity4 other) {
- copyFrom(other);
- }
-
- public FormatQuantity4(Number number) {
- if (number instanceof Long) {
- setToLong(number.longValue());
- } else if (number instanceof Integer) {
- setToInt(number.intValue());
- } else if (number instanceof Double) {
- setToDouble(number.doubleValue());
- } else if (number instanceof BigInteger) {
- setToBigInteger((BigInteger) number);
- } else if (number instanceof BigDecimal) {
- setToBigDecimal((BigDecimal) number);
- } else if (number instanceof com.ibm.icu.math.BigDecimal) {
- setToBigDecimal(((com.ibm.icu.math.BigDecimal) number).toBigDecimal());
- } else {
- throw new IllegalArgumentException(
- "Number is of an unsupported type: " + number.getClass().getName());
- }
- }
-
- @Override
- protected byte getDigitPos(int position) {
- if (usingBytes) {
- if (position < 0 || position > precision) return 0;
- return bcdBytes[position];
- } else {
- if (position < 0 || position >= 16) return 0;
- return (byte) ((bcdLong >>> (position * 4)) & 0xf);
- }
- }
-
- @Override
- protected void setDigitPos(int position, byte value) {
- assert position >= 0;
- if (usingBytes) {
- ensureCapacity(position + 1);
- bcdBytes[position] = value;
- } else if (position >= 16) {
- switchStorage();
- ensureCapacity(position + 1);
- bcdBytes[position] = value;
- } else {
- int shift = position * 4;
- bcdLong = bcdLong & ~(0xfL << shift) | ((long) value << shift);
- }
- }
-
- @Override
- protected void shiftLeft(int numDigits) {
- if (!usingBytes && precision + numDigits > 16) {
- switchStorage();
- }
- if (usingBytes) {
- ensureCapacity(precision + numDigits);
- int i = precision + numDigits - 1;
- for (; i >= numDigits; i--) {
- bcdBytes[i] = bcdBytes[i - numDigits];
- }
- for (; i >= 0; i--) {
- bcdBytes[i] = 0;
- }
- } else {
- bcdLong <<= (numDigits * 4);
- }
- scale -= numDigits;
- precision += numDigits;
- }
-
- @Override
- protected void shiftRight(int numDigits) {
- if (usingBytes) {
- int i = 0;
- for (; i < precision - numDigits; i++) {
- bcdBytes[i] = bcdBytes[i + numDigits];
- }
- for (; i < precision; i++) {
- bcdBytes[i] = 0;
- }
- } else {
- bcdLong >>>= (numDigits * 4);
- }
- scale += numDigits;
- precision -= numDigits;
- }
-
- @Override
- protected void setBcdToZero() {
- if (usingBytes) {
- for (int i = 0; i < precision; i++) {
- bcdBytes[i] = (byte) 0;
- }
- }
- usingBytes = false;
- bcdLong = 0L;
- scale = 0;
- precision = 0;
- isApproximate = false;
- origDouble = 0;
- origDelta = 0;
- }
-
- @Override
- protected void readIntToBcd(int n) {
- // ints always fit inside the long implementation.
- long result = 0L;
- int i = 16;
- for (; n != 0; n /= 10, i--) {
- result = (result >>> 4) + (((long) n % 10) << 60);
- }
- usingBytes = false;
- bcdLong = result >>> (i * 4);
- scale = 0;
- precision = 16 - i;
- }
-
- @Override
- protected void readLongToBcd(long n) {
- if (n >= 10000000000000000L) {
- ensureCapacity();
- int i = 0;
- for (; n != 0L; n /= 10L, i++) {
- bcdBytes[i] = (byte) (n % 10);
- }
- usingBytes = true;
- scale = 0;
- precision = i;
- } else {
- long result = 0L;
- int i = 16;
- for (; n != 0L; n /= 10L, i--) {
- result = (result >>> 4) + ((n % 10) << 60);
- }
- assert i >= 0;
- usingBytes = false;
- bcdLong = result >>> (i * 4);
- scale = 0;
- precision = 16 - i;
- }
- }
-
- @Override
- protected void readBigIntegerToBcd(BigInteger n) {
- ensureCapacity(); // allocate initial byte array
- int i = 0;
- for (; n.signum() != 0; i++) {
- BigInteger[] temp = n.divideAndRemainder(BigInteger.TEN);
- ensureCapacity(i + 1);
- bcdBytes[i] = temp[1].byteValue();
- n = temp[0];
- }
- usingBytes = true;
- scale = 0;
- precision = i;
- }
-
- @Override
- protected BigDecimal bcdToBigDecimal() {
- if (usingBytes) {
- // Converting to a string here is faster than doing BigInteger/BigDecimal arithmetic.
- StringBuilder sb = new StringBuilder();
- if (isNegative()) sb.append('-');
- assert precision > 0;
- for (int i = precision - 1; i >= 0; i--) {
- sb.append(getDigitPos(i));
- }
- if (scale != 0) {
- sb.append('E');
- sb.append(scale);
- }
- return new BigDecimal(sb.toString());
- } else {
- long tempLong = 0L;
- for (int shift = (precision - 1); shift >= 0; shift--) {
- tempLong = tempLong * 10 + getDigitPos(shift);
- }
- BigDecimal result = BigDecimal.valueOf(tempLong);
- result = result.scaleByPowerOfTen(scale);
- if (isNegative()) result = result.negate();
- return result;
- }
- }
-
- @Override
- protected void compact() {
- if (usingBytes) {
- int delta = 0;
- for (; delta < precision && bcdBytes[delta] == 0; delta++) ;
- if (delta == precision) {
- // Number is zero
- setBcdToZero();
- return;
- } else {
- // Remove trailing zeros
- shiftRight(delta);
- }
-
- // Compute precision
- int leading = precision - 1;
- for (; leading >= 0 && bcdBytes[leading] == 0; leading--) ;
- precision = leading + 1;
-
- // Switch storage mechanism if possible
- if (precision <= 16) {
- switchStorage();
- }
-
- } else {
- if (bcdLong == 0L) {
- // Number is zero
- setBcdToZero();
- return;
- }
-
- // Compact the number (remove trailing zeros)
- int delta = Long.numberOfTrailingZeros(bcdLong) / 4;
- bcdLong >>>= delta * 4;
- scale += delta;
-
- // Compute precision
- precision = 16 - (Long.numberOfLeadingZeros(bcdLong) / 4);
- }
- }
-
- /** Ensure that a byte array of at least 40 digits is allocated. */
- private void ensureCapacity() {
- ensureCapacity(40);
- }
-
- private void ensureCapacity(int capacity) {
- if (bcdBytes == null && capacity > 0) {
- bcdBytes = new byte[capacity];
- } else if (bcdBytes.length < capacity) {
- byte[] bcd1 = new byte[capacity * 2];
- System.arraycopy(bcdBytes, 0, bcd1, 0, bcdBytes.length);
- bcdBytes = bcd1;
- }
- }
-
- /** Switches the internal storage mechanism between the 64-bit long and the byte array. */
- private void switchStorage() {
- if (usingBytes) {
- // Change from bytes to long
- bcdLong = 0L;
- for (int i = precision - 1; i >= 0; i--) {
- bcdLong <<= 4;
- bcdLong |= bcdBytes[i];
- bcdBytes[i] = 0;
- }
- usingBytes = false;
- } else {
- // Change from long to bytes
- ensureCapacity();
- for (int i = 0; i < precision; i++) {
- bcdBytes[i] = (byte) (bcdLong & 0xf);
- bcdLong >>>= 4;
- }
- usingBytes = true;
- }
- }
-
- @Override
- protected void copyBcdFrom(FormatQuantity _other) {
- FormatQuantity4 other = (FormatQuantity4) _other;
- if (other.usingBytes) {
- usingBytes = true;
- ensureCapacity(other.precision);
- System.arraycopy(other.bcdBytes, 0, bcdBytes, 0, other.precision);
- } else {
- usingBytes = false;
- bcdLong = other.bcdLong;
- }
- }
-
- /**
- * Checks whether the bytes stored in this instance are all valid. For internal unit testing only.
- *
- * @return An error message if this instance is invalid, or null if this instance is healthy.
- * @internal
- * @deprecated This API is for ICU internal use only.
- */
- @Deprecated
- public String checkHealth() {
- if (usingBytes) {
- if (bcdLong != 0) return "Value in bcdLong but we are in byte mode";
- if (precision == 0) return "Zero precision but we are in byte mode";
- if (precision > bcdBytes.length) return "Precision exceeds length of byte array";
- if (getDigitPos(precision - 1) == 0) return "Most significant digit is zero in byte mode";
- if (getDigitPos(0) == 0) return "Least significant digit is zero in long mode";
- for (int i = 0; i < precision; i++) {
- if (getDigitPos(i) >= 10) return "Digit exceeding 10 in byte array";
- if (getDigitPos(i) < 0) return "Digit below 0 in byte array";
- }
- for (int i = precision; i < bcdBytes.length; i++) {
- if (getDigitPos(i) != 0) return "Nonzero digits outside of range in byte array";
- }
- } else {
- if (bcdBytes != null) {
- for (int i = 0; i < bcdBytes.length; i++) {
- if (bcdBytes[i] != 0) return "Nonzero digits in byte array but we are in long mode";
- }
- }
- if (precision == 0 && bcdLong != 0) return "Value in bcdLong even though precision is zero";
- if (precision > 16) return "Precision exceeds length of long";
- if (precision != 0 && getDigitPos(precision - 1) == 0)
- return "Most significant digit is zero in long mode";
- if (precision != 0 && getDigitPos(0) == 0)
- return "Least significant digit is zero in long mode";
- for (int i = 0; i < precision; i++) {
- if (getDigitPos(i) >= 10) return "Digit exceeding 10 in long";
- if (getDigitPos(i) < 0) return "Digit below 0 in long (?!)";
- }
- for (int i = precision; i < 16; i++) {
- if (getDigitPos(i) != 0) return "Nonzero digits outside of range in long";
- }
- }
-
- return null;
- }
-
- /**
- * Checks whether this {@link FormatQuantity4} is using its internal byte array storage mechanism.
- *
- * @return true if an internal byte array is being used; false if a long is being used.
- * @internal
- * @deprecated This API is ICU internal only.
- */
- @Deprecated
- public boolean usingBytes() {
- return usingBytes;
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- if (usingBytes) {
- for (int i = precision - 1; i >= 0; i--) {
- sb.append(bcdBytes[i]);
- }
- } else {
- sb.append(Long.toHexString(bcdLong));
- }
- return String.format(
- "<FormatQuantity4 %s:%d:%d:%s %s %s%s%d>",
- (lOptPos > 1000 ? "max" : String.valueOf(lOptPos)),
- lReqPos,
- rReqPos,
- (rOptPos < -1000 ? "min" : String.valueOf(rOptPos)),
- (usingBytes ? "bytes" : "long"),
- sb,
- "E",
- scale);
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number;
-
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.math.MathContext;
-import java.text.FieldPosition;
-
-import com.ibm.icu.impl.StandardPlural;
-import com.ibm.icu.text.PluralRules;
-import com.ibm.icu.text.PluralRules.Operand;
-import com.ibm.icu.text.UFieldPosition;
-
-/**
- * Represents numbers and digit display properties using Binary Coded Decimal (BCD).
- *
- * @implements {@link FormatQuantity}
- */
-public abstract class FormatQuantityBCD implements FormatQuantity {
-
- /**
- * The power of ten corresponding to the least significant digit in the BCD. For example, if this
- * object represents the number "3.14", the BCD will be "0x314" and the scale will be -2.
- *
- * <p>Note that in {@link java.math.BigDecimal}, the scale is defined differently: the number of
- * digits after the decimal place, which is the negative of our definition of scale.
- */
- protected int scale;
-
- /**
- * The number of digits in the BCD. For example, "1007" has BCD "0x1007" and precision 4. The
- * maximum precision is 16 since a long can hold only 16 digits.
- *
- * <p>This value must be re-calculated whenever the value in bcd changes by using {@link
- * #computePrecisionAndCompact()}.
- */
- protected int precision;
-
- /**
- * A bitmask of properties relating to the number represented by this object.
- *
- * @see #NEGATIVE_FLAG
- * @see #INFINITY_FLAG
- * @see #NAN_FLAG
- */
- protected int flags;
-
- protected static final int NEGATIVE_FLAG = 1;
- protected static final int INFINITY_FLAG = 2;
- protected static final int NAN_FLAG = 4;
-
- /**
- * The original number provided by the user and which is represented in BCD. Used when we need to
- * re-compute the BCD for an exact double representation.
- */
- protected double origDouble;
-
- protected int origDelta;
- protected boolean isApproximate;
-
- // Four positions: left optional '(', left required '[', right required ']', right optional ')'.
- // These four positions determine which digits are displayed in the output string. They do NOT
- // affect rounding. These positions are internal-only and can be specified only by the public
- // endpoints like setFractionLength, setIntegerLength, and setSignificantDigits, among others.
- //
- // * Digits between lReqPos and rReqPos are in the "required zone" and are always displayed.
- // * Digits between lOptPos and rOptPos but outside the required zone are in the "optional zone"
- // and are displayed unless they are trailing off the left or right edge of the number and
- // have a numerical value of zero. In order to be "trailing", the digits need to be beyond
- // the decimal point in their respective directions.
- // * Digits outside of the "optional zone" are never displayed.
- //
- // See the table below for illustrative examples.
- //
- // +---------+---------+---------+---------+------------+------------------------+--------------+
- // | lOptPos | lReqPos | rReqPos | rOptPos | number | positions | en-US string |
- // +---------+---------+---------+---------+------------+------------------------+--------------+
- // | 5 | 2 | -1 | -5 | 1234.567 | ( 12[34.5]67 ) | 1,234.567 |
- // | 3 | 2 | -1 | -5 | 1234.567 | 1(2[34.5]67 ) | 234.567 |
- // | 3 | 2 | -1 | -2 | 1234.567 | 1(2[34.5]6)7 | 234.56 |
- // | 6 | 4 | 2 | -5 | 123456789. | 123(45[67]89. ) | 456,789. |
- // | 6 | 4 | 2 | 1 | 123456789. | 123(45[67]8)9. | 456,780. |
- // | -1 | -1 | -3 | -4 | 0.123456 | 0.1([23]4)56 | .0234 |
- // | 6 | 4 | -2 | -2 | 12.3 | ( [ 12.3 ]) | 0012.30 |
- // +---------+---------+---------+---------+------------+------------------------+--------------+
- //
- protected int lOptPos = Integer.MAX_VALUE;
- protected int lReqPos = 0;
- protected int rReqPos = 0;
- protected int rOptPos = Integer.MIN_VALUE;
-
- @Override
- public void copyFrom(FormatQuantity _other) {
- copyBcdFrom(_other);
- FormatQuantityBCD other = (FormatQuantityBCD) _other;
- lOptPos = other.lOptPos;
- lReqPos = other.lReqPos;
- rReqPos = other.rReqPos;
- rOptPos = other.rOptPos;
- scale = other.scale;
- precision = other.precision;
- flags = other.flags;
- origDouble = other.origDouble;
- origDelta = other.origDelta;
- isApproximate = other.isApproximate;
- }
-
- public FormatQuantityBCD clear() {
- lOptPos = Integer.MAX_VALUE;
- lReqPos = 0;
- rReqPos = 0;
- rOptPos = Integer.MIN_VALUE;
- flags = 0;
- setBcdToZero(); // sets scale, precision, hasDouble, origDouble, origDelta, and BCD data
- return this;
- }
-
- @Override
- public void setIntegerFractionLength(int minInt, int maxInt, int minFrac, int maxFrac) {
- // Graceful failures for bogus input
- minInt = Math.max(0, minInt);
- maxInt = Math.max(0, maxInt);
- minFrac = Math.max(0, minFrac);
- maxFrac = Math.max(0, maxFrac);
-
- // The minima must be less than or equal to the maxima
- if (maxInt < minInt) {
- minInt = maxInt;
- }
- if (maxFrac < minFrac) {
- minFrac = maxFrac;
- }
-
- // Displaying neither integer nor fraction digits is not allowed
- if (maxInt == 0 && maxFrac == 0) {
- maxInt = Integer.MAX_VALUE;
- maxFrac = Integer.MAX_VALUE;
- }
-
- // Save values into internal state
- // Negation is safe for minFrac/maxFrac because -Integer.MAX_VALUE > Integer.MIN_VALUE
- lOptPos = maxInt;
- lReqPos = minInt;
- rReqPos = -minFrac;
- rOptPos = -maxFrac;
- }
-
- @Override
- public long getPositionFingerprint() {
- long fingerprint = 0;
- fingerprint ^= lOptPos;
- fingerprint ^= (lReqPos << 16);
- fingerprint ^= ((long) rReqPos << 32);
- fingerprint ^= ((long) rOptPos << 48);
- return fingerprint;
- }
-
- @Override
- public void roundToIncrement(BigDecimal roundingInterval, MathContext mathContext) {
- // TODO: Avoid converting back and forth to BigDecimal.
- BigDecimal temp = toBigDecimal();
- temp =
- temp.divide(roundingInterval, 0, mathContext.getRoundingMode())
- .multiply(roundingInterval)
- .round(mathContext);
- if (temp.signum() == 0) {
- setBcdToZero(); // keeps negative flag for -0.0
- } else {
- setToBigDecimal(temp);
- }
- }
-
- @Override
- public void multiplyBy(BigDecimal multiplicand) {
- BigDecimal temp = toBigDecimal();
- temp = temp.multiply(multiplicand);
- setToBigDecimal(temp);
- }
-
- @Override
- public int getMagnitude() throws ArithmeticException {
- if (precision == 0) {
- throw new ArithmeticException("Magnitude is not well-defined for zero");
- } else {
- return scale + precision - 1;
- }
- }
-
- @Override
- public void adjustMagnitude(int delta) {
- if (precision != 0) {
- scale += delta;
- origDelta += delta;
- }
- }
-
- @Override
- public StandardPlural getStandardPlural(PluralRules rules) {
- if (rules == null) {
- // Fail gracefully if the user didn't provide a PluralRules
- return StandardPlural.OTHER;
- } else {
- @SuppressWarnings("deprecation")
- String ruleString = rules.select(this);
- return StandardPlural.orOtherFromString(ruleString);
- }
- }
-
- @Override
- public double getPluralOperand(Operand operand) {
- switch (operand) {
- case i:
- return toLong();
- case f:
- return toFractionLong(true);
- case t:
- return toFractionLong(false);
- case v:
- return fractionCount();
- case w:
- return fractionCountWithoutTrailingZeros();
- default:
- return Math.abs(toDouble());
- }
- }
-
- /**
- * If the given {@link FieldPosition} is a {@link UFieldPosition}, populates it with the fraction
- * length and fraction long value. If the argument is not a {@link UFieldPosition}, nothing
- * happens.
- *
- * @param fp The {@link UFieldPosition} to populate.
- */
- public void populateUFieldPosition(FieldPosition fp) {
- if (fp instanceof UFieldPosition) {
- ((UFieldPosition) fp)
- .setFractionDigits((int) getPluralOperand(Operand.v), (long) getPluralOperand(Operand.f));
- }
- }
-
- @Override
- public int getUpperDisplayMagnitude() {
- int magnitude = scale + precision;
- int result = (lReqPos > magnitude) ? lReqPos : (lOptPos < magnitude) ? lOptPos : magnitude;
- return result - 1;
- }
-
- @Override
- public int getLowerDisplayMagnitude() {
- int magnitude = scale;
- int result = (rReqPos < magnitude) ? rReqPos : (rOptPos > magnitude) ? rOptPos : magnitude;
- return result;
- }
-
- @Override
- public byte getDigit(int magnitude) {
- return getDigitPos(magnitude - scale);
- }
-
- private int fractionCount() {
- return -getLowerDisplayMagnitude();
- }
-
- private int fractionCountWithoutTrailingZeros() {
- return Math.max(-scale, 0);
- }
-
- @Override
- public boolean isNegative() {
- return (flags & NEGATIVE_FLAG) != 0;
- }
-
- @Override
- public boolean isInfinite() {
- return (flags & INFINITY_FLAG) != 0;
- }
-
- @Override
- public boolean isNaN() {
- return (flags & NAN_FLAG) != 0;
- }
-
- @Override
- public boolean isZero() {
- return precision == 0;
- }
-
- @Override
- public FormatQuantity clone() {
- if (this instanceof FormatQuantity2) {
- return new FormatQuantity2((FormatQuantity2) this);
- } else if (this instanceof FormatQuantity3) {
- return new FormatQuantity3((FormatQuantity3) this);
- } else if (this instanceof FormatQuantity4) {
- return new FormatQuantity4((FormatQuantity4) this);
- } else {
- throw new IllegalArgumentException("Don't know how to clone " + this.getClass());
- }
- }
-
- public void setToInt(int n) {
- setBcdToZero();
- flags = 0;
- if (n < 0) {
- flags |= NEGATIVE_FLAG;
- n = -n;
- }
- if (n != 0) {
- _setToInt(n);
- compact();
- }
- }
-
- private void _setToInt(int n) {
- if (n == Integer.MIN_VALUE) {
- readLongToBcd(-(long) n);
- } else {
- readIntToBcd(n);
- }
- }
-
- public void setToLong(long n) {
- setBcdToZero();
- flags = 0;
- if (n < 0) {
- flags |= NEGATIVE_FLAG;
- n = -n;
- }
- if (n != 0) {
- _setToLong(n);
- compact();
- }
- }
-
- private void _setToLong(long n) {
- if (n == Long.MIN_VALUE) {
- readBigIntegerToBcd(BigInteger.valueOf(n).negate());
- } else if (n <= Integer.MAX_VALUE) {
- readIntToBcd((int) n);
- } else {
- readLongToBcd(n);
- }
- }
-
- public void setToBigInteger(BigInteger n) {
- setBcdToZero();
- flags = 0;
- if (n.signum() == -1) {
- flags |= NEGATIVE_FLAG;
- n = n.negate();
- }
- if (n.signum() != 0) {
- _setToBigInteger(n);
- compact();
- }
- }
-
- private void _setToBigInteger(BigInteger n) {
- if (n.bitLength() < 32) {
- readIntToBcd(n.intValue());
- } else if (n.bitLength() < 64) {
- readLongToBcd(n.longValue());
- } else {
- readBigIntegerToBcd(n);
- }
- }
-
- /**
- * Sets the internal BCD state to represent the value in the given double.
- *
- * @param n The value to consume.
- */
- public void setToDouble(double n) {
- setBcdToZero();
- flags = 0;
- // Double.compare() handles +0.0 vs -0.0
- if (Double.compare(n, 0.0) < 0) {
- flags |= NEGATIVE_FLAG;
- n = -n;
- }
- if (Double.isNaN(n)) {
- flags |= NAN_FLAG;
- } else if (Double.isInfinite(n)) {
- flags |= INFINITY_FLAG;
- } else if (n != 0) {
- _setToDoubleFast(n);
-
- // TODO: Remove this when finished testing.
- // isApproximate = true;
- // origDouble = n;
- // origDelta = 0;
- // convertToAccurateDouble();
-
- compact();
- }
- }
-
- private static final double[] DOUBLE_MULTIPLIERS = {
- 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16,
- 1e17, 1e18, 1e19, 1e20, 1e21
- };
-
- /**
- * Uses double multiplication and division to get the number into integer space before converting
- * to digits. Since double arithmetic is inexact, the resulting digits may not be accurate.
- */
- private void _setToDoubleFast(double n) {
- long ieeeBits = Double.doubleToLongBits(n);
- int exponent = (int) ((ieeeBits & 0x7ff0000000000000L) >> 52) - 0x3ff;
-
- // Not all integers can be represented exactly for exponent > 52
- if (exponent <= 52 && (long) n == n) {
- _setToLong((long) n);
- return;
- }
-
- isApproximate = true;
- origDouble = n;
- origDelta = 0;
-
- int fracLength = (int) ((52 - exponent) / 3.32192809489);
- if (fracLength >= 0) {
- int i = fracLength;
- // 1e22 is the largest exact double.
- for (; i >= 22; i -= 22) n *= 1e22;
- n *= DOUBLE_MULTIPLIERS[i];
- } else {
- int i = fracLength;
- // 1e22 is the largest exact double.
- for (; i <= -22; i += 22) n /= 1e22;
- n /= DOUBLE_MULTIPLIERS[-i];
- }
- _setToLong(Math.round(n));
- scale -= fracLength;
- }
-
- /**
- * Uses Double.toString() to obtain an exact accurate representation of the double, overwriting it
- * into the BCD. This method can be called at any point after {@link #_setToDoubleFast} while
- * {@link #isApproximate} is still true.
- */
- private void convertToAccurateDouble() {
- double n = origDouble;
- assert n != 0;
- int delta = origDelta;
- setBcdToZero();
-
- // Call the slow oracle function
- String temp = Double.toString(n);
-
- if (temp.indexOf('E') != -1) {
- // Case 1: Exponential notation.
- assert temp.indexOf('.') == 1;
- int expPos = temp.indexOf('E');
- _setToLong(Long.parseLong(temp.charAt(0) + temp.substring(2, expPos)));
- scale += Integer.parseInt(temp.substring(expPos + 1)) - (expPos - 1) + 1;
- } else if (temp.charAt(0) == '0') {
- // Case 2: Fraction-only number.
- assert temp.indexOf('.') == 1;
- _setToLong(Long.parseLong(temp.substring(2)));
- scale += 2 - temp.length();
- } else if (temp.charAt(temp.length() - 1) == '0') {
- // Case 3: Integer-only number.
- // Note: this path should not normally happen, because integer-only numbers are captured
- // before the approximate double logic is performed.
- assert temp.indexOf('.') == temp.length() - 2;
- assert temp.length() - 2 <= 18;
- _setToLong(Long.parseLong(temp.substring(0, temp.length() - 2)));
- // no need to adjust scale
- } else {
- // Case 4: Number with both a fraction and an integer.
- int decimalPos = temp.indexOf('.');
- _setToLong(Long.parseLong(temp.substring(0, decimalPos) + temp.substring(decimalPos + 1)));
- scale += decimalPos - temp.length() + 1;
- }
- scale += delta;
- compact();
- explicitExactDouble = true;
- }
-
- /**
- * Whether this {@link FormatQuantity4} has been explicitly converted to an exact double. true if
- * backed by a double that was explicitly converted via convertToAccurateDouble; false otherwise.
- * Used for testing.
- *
- * @internal
- * @deprecated This API is ICU internal only.
- */
- @Deprecated public boolean explicitExactDouble = false;
-
- /**
- * Sets the internal BCD state to represent the value in the given BigDecimal.
- *
- * @param n The value to consume.
- */
- public void setToBigDecimal(BigDecimal n) {
- setBcdToZero();
- flags = 0;
- if (n.signum() == -1) {
- flags |= NEGATIVE_FLAG;
- n = n.negate();
- }
- if (n.signum() != 0) {
- _setToBigDecimal(n);
- compact();
- }
- }
-
- private void _setToBigDecimal(BigDecimal n) {
- int fracLength = n.scale();
- n = n.scaleByPowerOfTen(fracLength);
- BigInteger bi = n.toBigInteger();
- _setToBigInteger(bi);
- scale -= fracLength;
- }
-
- /**
- * Returns a long approximating the internal BCD. A long can only represent the integral part of
- * the number.
- *
- * @return A double representation of the internal BCD.
- */
- protected long toLong() {
- long result = 0L;
- for (int magnitude = scale + precision - 1; magnitude >= 0; magnitude--) {
- result = result * 10 + getDigitPos(magnitude - scale);
- }
- return result;
- }
-
- /**
- * This returns a long representing the fraction digits of the number, as required by PluralRules.
- * For example, if we represent the number "1.20" (including optional and required digits), then
- * this function returns "20" if includeTrailingZeros is true or "2" if false.
- */
- protected long toFractionLong(boolean includeTrailingZeros) {
- long result = 0L;
- int magnitude = -1;
- for (;
- (magnitude >= scale || (includeTrailingZeros && magnitude >= rReqPos))
- && magnitude >= rOptPos;
- magnitude--) {
- result = result * 10 + getDigitPos(magnitude - scale);
- }
- return result;
- }
-
- /**
- * Returns a double approximating the internal BCD. The double may not retain all of the
- * information encoded in the BCD if the BCD represents a number out of range of a double.
- *
- * @return A double representation of the internal BCD.
- */
- @Override
- public double toDouble() {
- if (isApproximate) {
- return toDoubleFromOriginal();
- }
-
- if (isNaN()) {
- return Double.NaN;
- } else if (isInfinite()) {
- return isNegative() ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY;
- }
-
- long tempLong = 0L;
- int lostDigits = precision - Math.min(precision, 17);
- for (int shift = precision - 1; shift >= lostDigits; shift--) {
- tempLong = tempLong * 10 + getDigitPos(shift);
- }
- double result = tempLong;
- int _scale = scale + lostDigits;
- if (_scale >= 0) {
- int i = _scale;
- for (; i >= 9; i -= 9) result *= 1000000000;
- for (; i >= 3; i -= 3) result *= 1000;
- for (; i >= 1; i -= 1) result *= 10;
- } else {
- int i = _scale;
- for (; i <= -9; i += 9) result /= 1000000000;
- for (; i <= -3; i += 3) result /= 1000;
- for (; i <= -1; i += 1) result /= 10;
- }
- if (isNegative()) result = -result;
- return result;
- }
-
- @Override
- public BigDecimal toBigDecimal() {
- if (isApproximate) {
- // Converting to a BigDecimal requires Double.toString().
- convertToAccurateDouble();
- }
- return bcdToBigDecimal();
- }
-
- protected double toDoubleFromOriginal() {
- double result = origDouble;
- double delta = origDelta;
- if (delta >= 0) {
- for (; delta >= 9; delta -= 9) result *= 1000000000;
- for (; delta >= 3; delta -= 3) result *= 1000;
- for (; delta >= 1; delta -= 1) result *= 10;
- } else {
- for (; delta <= -9; delta += 9) result /= 1000000000;
- for (; delta <= -3; delta += 3) result /= 1000;
- for (; delta <= -1; delta += 1) result /= 10;
- }
- if (isNegative()) result *= -1;
- return result;
- }
-
- private static int safeSubtract(int a, int b) {
- if (b < 0 && a - b < a) return Integer.MAX_VALUE;
- if (b > 0 && a - b > a) return Integer.MIN_VALUE;
- return a - b;
- }
-
- @Override
- public void roundToMagnitude(int magnitude, MathContext mathContext) {
- // The position in the BCD at which rounding will be performed; digits to the right of position
- // will be rounded away.
- // TODO: Andy: There was a test failure because of integer overflow here. Should I do
- // "safe subtraction" everywhere in the code? What's the nicest way to do it?
- int position = safeSubtract(magnitude, scale);
-
- // Enforce the number of digits required by the MathContext.
- int _mcPrecision = mathContext.getPrecision();
- if (magnitude == Integer.MAX_VALUE
- || (_mcPrecision > 0 && precision - position > _mcPrecision)) {
- position = precision - _mcPrecision;
- }
-
- if (position <= 0 && !isApproximate) {
- // All digits are to the left of the rounding magnitude.
- } else if (precision == 0) {
- // No rounding for zero.
- } else {
- // Perform rounding logic.
- // "leading" = most significant digit to the right of rounding
- // "trailing" = least significant digit to the left of rounding
- byte leadingDigit = getDigitPos(safeSubtract(position, 1));
- byte trailingDigit = getDigitPos(position);
-
- // Compute which section of the number we are in.
- // EDGE means we are at the bottom or top edge, like 1.000 or 1.999 (used by doubles)
- // LOWER means we are between the bottom edge and the midpoint, like 1.391
- // MIDPOINT means we are exactly in the middle, like 1.500
- // UPPER means we are between the midpoint and the top edge, like 1.916
- int section = RoundingUtils.SECTION_MIDPOINT;
- if (!isApproximate) {
- if (leadingDigit < 5) {
- section = RoundingUtils.SECTION_LOWER;
- } else if (leadingDigit > 5) {
- section = RoundingUtils.SECTION_UPPER;
- } else {
- for (int p = safeSubtract(position, 2); p >= 0; p--) {
- if (getDigitPos(p) != 0) {
- section = RoundingUtils.SECTION_UPPER;
- break;
- }
- }
- }
- } else {
- int p = safeSubtract(position, 2);
- int minP = Math.max(0, precision - 14);
- if (leadingDigit == 0) {
- section = -1;
- for (; p >= minP; p--) {
- if (getDigitPos(p) != 0) {
- section = RoundingUtils.SECTION_LOWER;
- break;
- }
- }
- } else if (leadingDigit == 4) {
- for (; p >= minP; p--) {
- if (getDigitPos(p) != 9) {
- section = RoundingUtils.SECTION_LOWER;
- break;
- }
- }
- } else if (leadingDigit == 5) {
- for (; p >= minP; p--) {
- if (getDigitPos(p) != 0) {
- section = RoundingUtils.SECTION_UPPER;
- break;
- }
- }
- } else if (leadingDigit == 9) {
- section = -2;
- for (; p >= minP; p--) {
- if (getDigitPos(p) != 9) {
- section = RoundingUtils.SECTION_UPPER;
- break;
- }
- }
- } else if (leadingDigit < 5) {
- section = RoundingUtils.SECTION_LOWER;
- } else {
- section = RoundingUtils.SECTION_UPPER;
- }
-
- boolean roundsAtMidpoint =
- RoundingUtils.roundsAtMidpoint(mathContext.getRoundingMode().ordinal());
- if (safeSubtract(position, 1) < precision - 14
- || (roundsAtMidpoint && section == RoundingUtils.SECTION_MIDPOINT)
- || (!roundsAtMidpoint && section < 0 /* i.e. at upper or lower edge */)) {
- // Oops! This means that we have to get the exact representation of the double, because
- // the zone of uncertainty is along the rounding boundary.
- convertToAccurateDouble();
- roundToMagnitude(magnitude, mathContext); // start over
- return;
- }
-
- // Turn off the approximate double flag, since the value is now confirmed to be exact.
- isApproximate = false;
- origDouble = 0.0;
- origDelta = 0;
-
- if (position <= 0) {
- // All digits are to the left of the rounding magnitude.
- return;
- }
-
- // Good to continue rounding.
- if (section == -1) section = RoundingUtils.SECTION_LOWER;
- if (section == -2) section = RoundingUtils.SECTION_UPPER;
- }
-
- boolean roundDown =
- RoundingUtils.getRoundingDirection(
- (trailingDigit % 2) == 0,
- isNegative(),
- section,
- mathContext.getRoundingMode().ordinal(),
- this);
-
- // Perform truncation
- if (position >= precision) {
- setBcdToZero();
- scale = magnitude;
- } else {
- shiftRight(position);
- }
-
- // Bubble the result to the higher digits
- if (!roundDown) {
- if (trailingDigit == 9) {
- int bubblePos = 0;
- // Note: in the long implementation, the most digits BCD can have at this point is 15,
- // so bubblePos <= 15 and getDigitPos(bubblePos) is safe.
- for (; getDigitPos(bubblePos) == 9; bubblePos++) {}
- shiftRight(bubblePos); // shift off the trailing 9s
- }
- byte digit0 = getDigitPos(0);
- assert digit0 != 9;
- setDigitPos(0, (byte) (digit0 + 1));
- precision += 1; // in case an extra digit got added
- }
-
- compact();
- }
- }
-
- @Override
- public void roundToInfinity() {
- if (isApproximate) {
- convertToAccurateDouble();
- }
- }
-
- /**
- * Appends a digit, optionally with one or more leading zeros, to the end of the value represented
- * by this FormatQuantity.
- *
- * <p>The primary use of this method is to construct numbers during a parsing loop. It allows
- * parsing to take advantage of the digit list infrastructure primarily designed for formatting.
- *
- * @param value The digit to append.
- * @param leadingZeros The number of zeros to append before the digit. For example, if the value
- * in this instance starts as 12.3, and you append a 4 with 1 leading zero, the value becomes
- * 12.304.
- * @param appendAsInteger If true, increase the magnitude of existing digits to make room for the
- * new digit. If false, append to the end like a fraction digit. If true, there must not be
- * any fraction digits already in the number.
- * @internal
- * @deprecated This API is ICU internal only.
- */
- @Deprecated
- public void appendDigit(byte value, int leadingZeros, boolean appendAsInteger) {
- assert leadingZeros >= 0;
-
- // Zero requires special handling to maintain the invariant that the least-significant digit
- // in the BCD is nonzero.
- if (value == 0) {
- if (appendAsInteger && precision != 0) {
- scale += leadingZeros + 1;
- }
- return;
- }
-
- // Deal with trailing zeros
- if (scale > 0) {
- leadingZeros += scale;
- if (appendAsInteger) {
- scale = 0;
- }
- }
-
- // Append digit
- shiftLeft(leadingZeros + 1);
- setDigitPos(0, value);
-
- // Fix scale if in integer mode
- if (appendAsInteger) {
- scale += leadingZeros + 1;
- }
- }
-
- /**
- * Returns a single digit from the BCD list. No internal state is changed by calling this method.
- *
- * @param position The position of the digit to pop, counted in BCD units from the least
- * significant digit. If outside the range supported by the implementation, zero is returned.
- * @return The digit at the specified location.
- */
- protected abstract byte getDigitPos(int position);
-
- /**
- * Sets the digit in the BCD list. This method only sets the digit; it is the caller's
- * responsibility to call {@link #compact} after setting the digit.
- *
- * @param position The position of the digit to pop, counted in BCD units from the least
- * significant digit. If outside the range supported by the implementation, an AssertionError
- * is thrown.
- * @param value The digit to set at the specified location.
- */
- protected abstract void setDigitPos(int position, byte value);
-
- /**
- * Adds zeros to the end of the BCD list. This will result in an invalid BCD representation; it is
- * the caller's responsibility to do further manipulation and then call {@link #compact}.
- *
- * @param numDigits The number of zeros to add.
- */
- protected abstract void shiftLeft(int numDigits);
-
- protected abstract void shiftRight(int numDigits);
-
- /**
- * Sets the internal representation to zero. Clears any values stored in scale, precision,
- * hasDouble, origDouble, origDelta, and BCD data.
- */
- protected abstract void setBcdToZero();
-
- /**
- * Sets the internal BCD state to represent the value in the given int. The int is guaranteed to
- * be either positive. The internal state is guaranteed to be empty when this method is called.
- *
- * @param n The value to consume.
- */
- protected abstract void readIntToBcd(int input);
-
- /**
- * Sets the internal BCD state to represent the value in the given long. The long is guaranteed to
- * be either positive. The internal state is guaranteed to be empty when this method is called.
- *
- * @param n The value to consume.
- */
- protected abstract void readLongToBcd(long input);
-
- /**
- * Sets the internal BCD state to represent the value in the given BigInteger. The BigInteger is
- * guaranteed to be positive, and it is guaranteed to be larger than Long.MAX_VALUE. The internal
- * state is guaranteed to be empty when this method is called.
- *
- * @param n The value to consume.
- */
- protected abstract void readBigIntegerToBcd(BigInteger input);
-
- /**
- * Returns a BigDecimal encoding the internal BCD value.
- *
- * @return A BigDecimal representation of the internal BCD.
- */
- protected abstract BigDecimal bcdToBigDecimal();
-
- protected abstract void copyBcdFrom(FormatQuantity _other);
-
- /**
- * Removes trailing zeros from the BCD (adjusting the scale as required) and then computes the
- * precision. The precision is the number of digits in the number up through the greatest nonzero
- * digit.
- *
- * <p>This method must always be called when bcd changes in order for assumptions to be correct in
- * methods like {@link #fractionCount()}.
- */
- protected abstract void compact();
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number;
-
-import java.math.BigDecimal;
-import java.math.BigInteger;
-
-/** @author sffc */
-public class FormatQuantitySelector {
- public static FormatQuantityBCD from(int input) {
- return new FormatQuantity4(input);
- }
-
- public static FormatQuantityBCD from(long input) {
- return new FormatQuantity4(input);
- }
-
- public static FormatQuantityBCD from(double input) {
- return new FormatQuantity4(input);
- }
-
- public static FormatQuantityBCD from(BigInteger input) {
- return new FormatQuantity4(input);
- }
-
- public static FormatQuantityBCD from(BigDecimal input) {
- return new FormatQuantity4(input);
- }
-
- public static FormatQuantityBCD from(com.ibm.icu.math.BigDecimal input) {
- return from(input.toBigDecimal());
- }
-
- public static FormatQuantityBCD from(Number number) {
- if (number instanceof Long) {
- return from(number.longValue());
- } else if (number instanceof Integer) {
- return from(number.intValue());
- } else if (number instanceof Double) {
- return from(number.doubleValue());
- } else if (number instanceof BigInteger) {
- return from((BigInteger) number);
- } else if (number instanceof BigDecimal) {
- return from((BigDecimal) number);
- } else if (number instanceof com.ibm.icu.math.BigDecimal) {
- return from((com.ibm.icu.math.BigDecimal) number);
- } else {
- throw new IllegalArgumentException(
- "Number is of an unsupported type: " + number.getClass().getName());
- }
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number;
-
-import com.ibm.icu.impl.StandardPlural;
-import com.ibm.icu.impl.number.modifiers.ConstantAffixModifier;
-import com.ibm.icu.impl.number.modifiers.GeneralPluralModifier;
-import com.ibm.icu.impl.number.modifiers.PositiveNegativeAffixModifier;
-import com.ibm.icu.impl.number.modifiers.SimpleModifier;
-
-/**
- * A Modifier is an immutable object that can be passed through the formatting pipeline until it is
- * finally applied to the string builder. A Modifier usually contains a prefix and a suffix that are
- * applied, but it could contain something else, like a {@link com.ibm.icu.text.SimpleFormatter}
- * pattern.
- *
- * @see PositiveNegativeAffixModifier
- * @see ConstantAffixModifier
- * @see GeneralPluralModifier
- * @see SimpleModifier
- */
-public interface Modifier {
-
- /**
- * Apply this Modifier to the string builder.
- *
- * @param output The string builder to which to apply this modifier.
- * @param leftIndex The left index of the string within the builder. Equal to 0 when only one
- * number is being formatted.
- * @param rightIndex The right index of the string within the string builder. Equal to length-1
- * when only one number is being formatted.
- * @return The number of characters (UTF-16 code units) that were added to the string builder.
- */
- public int apply(NumberStringBuilder output, int leftIndex, int rightIndex);
-
- /**
- * The number of characters that {@link #apply} would add to the string builder.
- *
- * @return The number of characters (UTF-16 code units) that would be added to a string builder.
- */
- public int length();
-
- /**
- * Whether this modifier is strong. If a modifier is strong, it should always be applied
- * immediately and not allowed to bubble up. With regard to padding, strong modifiers are
- * considered to be on the inside of the prefix and suffix.
- *
- * @return Whether the modifier is strong.
- */
- public boolean isStrong();
-
- /**
- * Gets the prefix string associated with this modifier, defined as the string that will be
- * inserted at leftIndex when {@link #apply} is called.
- *
- * @return The prefix string. Will not be null.
- */
- public String getPrefix();
-
- /**
- * Gets the prefix string associated with this modifier, defined as the string that will be
- * inserted at rightIndex when {@link #apply} is called.
- *
- * @return The suffix string. Will not be null.
- */
- public String getSuffix();
-
- /**
- * An interface for a modifier that contains both a positive and a negative form. Note that a
- * class implementing {@link PositiveNegativeModifier} is not necessarily a {@link Modifier}
- * itself. Rather, it returns a {@link Modifier} when {@link #getModifier} is called.
- */
- public static interface PositiveNegativeModifier extends Exportable {
- /**
- * Converts this {@link PositiveNegativeModifier} to a {@link Modifier} given the negative sign.
- *
- * @param isNegative true if the negative form of this modifier should be used; false if the
- * positive form should be used.
- * @return A Modifier corresponding to the negative sign.
- */
- public Modifier getModifier(boolean isNegative);
- }
-
- /**
- * An interface for a modifier that contains both a positive and a negative form for all six
- * standard plurals. Note that a class implementing {@link PositiveNegativePluralModifier} is not
- * necessarily a {@link Modifier} itself. Rather, it returns a {@link Modifier} when {@link
- * #getModifier} is called.
- */
- public static interface PositiveNegativePluralModifier extends Exportable {
- /**
- * Converts this {@link PositiveNegativePluralModifier} to a {@link Modifier} given the negative
- * sign and the standard plural.
- *
- * @param plural The StandardPlural to use.
- * @param isNegative true if the negative form of this modifier should be used; false if the
- * positive form should be used.
- * @return A Modifier corresponding to the negative sign.
- */
- public Modifier getModifier(StandardPlural plural, boolean isNegative);
- }
-
- /**
- * An interface for a modifier that is represented internally by a prefix string and a suffix
- * string.
- */
- public static interface AffixModifier extends Modifier {}
-
- /**
- * A starter implementation with defaults for some of the basic methods.
- *
- * <p>Implements {@link PositiveNegativeModifier} only so that instances of this class can be used when
- * a {@link PositiveNegativeModifier} is required.
- */
- public abstract static class BaseModifier extends Format.BeforeFormat
- implements Modifier, PositiveNegativeModifier {
-
- @Override
- public void before(FormatQuantity input, ModifierHolder mods) {
- mods.add(this);
- }
-
- @Override
- public Modifier getModifier(boolean isNegative) {
- return this;
- }
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number;
-
-import java.util.ArrayDeque;
-
-public class ModifierHolder {
- private ArrayDeque<Modifier> mods = new ArrayDeque<Modifier>();
-
- // Using five separate fields instead of the ArrayDeque saves about 10ns at the expense of
- // worse code.
- // TODO: Decide which implementation to use.
-
- // private Modifier mod1 = null;
- // private Modifier mod2 = null;
- // private Modifier mod3 = null;
- // private Modifier mod4 = null;
- // private Modifier mod5 = null;
-
- public ModifierHolder clear() {
- // mod1 = null;
- // mod2 = null;
- // mod3 = null;
- // mod4 = null;
- // mod5 = null;
- mods.clear();
- return this;
- }
-
- public void add(Modifier modifier) {
- // if (mod1 == null) {
- // mod1 = modifier;
- // } else if (mod2 == null) {
- // mod2 = modifier;
- // } else if (mod3 == null) {
- // mod3 = modifier;
- // } else if (mod4 == null) {
- // mod4 = modifier;
- // } else if (mod5 == null) {
- // mod5 = modifier;
- // } else {
- // throw new IndexOutOfBoundsException();
- // }
- if (modifier != null) mods.addFirst(modifier);
- }
-
- public Modifier peekLast() {
- return mods.peekLast();
- }
-
- public Modifier removeLast() {
- return mods.removeLast();
- }
-
- public int applyAll(NumberStringBuilder string, int leftIndex, int rightIndex) {
- int addedLength = 0;
- // if (mod5 != null) {
- // addedLength += mod5.apply(string, leftIndex, rightIndex + addedLength);
- // mod5 = null;
- // }
- // if (mod4 != null) {
- // addedLength += mod4.apply(string, leftIndex, rightIndex + addedLength);
- // mod4 = null;
- // }
- // if (mod3 != null) {
- // addedLength += mod3.apply(string, leftIndex, rightIndex + addedLength);
- // mod3 = null;
- // }
- // if (mod2 != null) {
- // addedLength += mod2.apply(string, leftIndex, rightIndex + addedLength);
- // mod2 = null;
- // }
- // if (mod1 != null) {
- // addedLength += mod1.apply(string, leftIndex, rightIndex + addedLength);
- // mod1 = null;
- // }
- while (!mods.isEmpty()) {
- Modifier mod = mods.removeFirst();
- addedLength += mod.apply(string, leftIndex, rightIndex + addedLength);
- }
- return addedLength;
- }
-
- public int applyStrong(NumberStringBuilder string, int leftIndex, int rightIndex) {
- int addedLength = 0;
- while (!mods.isEmpty() && mods.peekFirst().isStrong()) {
- Modifier mod = mods.removeFirst();
- addedLength += mod.apply(string, leftIndex, rightIndex + addedLength);
- }
- return addedLength;
- }
-
- public int totalLength() {
- int length = 0;
- // if (mod1 != null) length += mod1.length();
- // if (mod2 != null) length += mod2.length();
- // if (mod3 != null) length += mod3.length();
- // if (mod4 != null) length += mod4.length();
- // if (mod5 != null) length += mod5.length();
- for (Modifier mod : mods) {
- if (mod == null) continue;
- length += mod.length();
- }
- return length;
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number;
-
-import java.text.AttributedCharacterIterator;
-import java.text.AttributedString;
-import java.text.FieldPosition;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
-
-import com.ibm.icu.text.NumberFormat;
-import com.ibm.icu.text.NumberFormat.Field;
-
-public class NumberStringBuilder implements CharSequence {
- private char[] chars;
- private Field[] fields;
- private int zero;
- private int length;
-
- public NumberStringBuilder() {
- this(40);
- }
-
- public NumberStringBuilder(int capacity) {
- chars = new char[capacity];
- fields = new Field[capacity];
- zero = capacity / 2;
- length = 0;
- }
-
- @Override
- public int length() {
- return length;
- }
-
- @Override
- public char charAt(int index) {
- if (index < 0 || index > length) {
- throw new IndexOutOfBoundsException();
- }
- return chars[zero + index];
- }
-
- /**
- * Appends the specified codePoint to the end of the string.
- *
- * @return The number of chars added: 1 if the code point is in the BMP, or 2 otherwise.
- */
- public int appendCodePoint(int codePoint, Field field) {
- return insertCodePoint(length, codePoint, field);
- }
-
- /**
- * Inserts the specified codePoint at the specified index in the string.
- *
- * @return The number of chars added: 1 if the code point is in the BMP, or 2 otherwise.
- */
- public int insertCodePoint(int index, int codePoint, Field field) {
- int count = Character.charCount(codePoint);
- int position = prepareForInsert(index, count);
- Character.toChars(codePoint, chars, position);
- fields[position] = field;
- if (count == 2) fields[position + 1] = field;
- return count;
- }
-
- /**
- * Appends the specified CharSequence to the end of the string.
- *
- * @return The number of chars added, which is the length of CharSequence.
- */
- public int append(CharSequence sequence, Field field) {
- return insert(length, sequence, field);
- }
-
- /**
- * Inserts the specified CharSequence at the specified index in the string.
- *
- * @return The number of chars added, which is the length of CharSequence.
- */
- public int insert(int index, CharSequence sequence, Field field) {
- if (sequence.length() == 0) {
- // Nothing to insert.
- return 0;
- } else if (sequence.length() == 1) {
- // Fast path: on a single-char string, using insertCodePoint below is 70% faster than the
- // CharSequence method: 12.2 ns versus 41.9 ns for five operations on my Linux x86-64.
- return insertCodePoint(index, sequence.charAt(0), field);
- } else {
- return insert(index, sequence, 0, sequence.length(), field);
- }
- }
-
- /**
- * Inserts the specified CharSequence at the specified index in the string, reading from the
- * CharSequence from start (inclusive) to end (exclusive).
- *
- * @return The number of chars added, which is the length of CharSequence.
- */
- public int insert(int index, CharSequence sequence, int start, int end, Field field) {
- int count = end - start;
- int position = prepareForInsert(index, count);
- for (int i = 0; i < count; i++) {
- chars[position + i] = sequence.charAt(start + i);
- fields[position + i] = field;
- }
- return count;
- }
-
- /**
- * Appends the chars in the specified char array to the end of the string, and associates them
- * with the fields in the specified field array, which must have the same length as chars.
- *
- * @return The number of chars added, which is the length of the char array.
- */
- public int append(char[] chars, Field[] fields) {
- return insert(length, chars, fields);
- }
-
- /**
- * Inserts the chars in the specified char array at the specified index in the string, and
- * associates them with the fields in the specified field array, which must have the same length
- * as chars.
- *
- * @return The number of chars added, which is the length of the char array.
- */
- public int insert(int index, char[] chars, Field[] fields) {
- assert fields == null || chars.length == fields.length;
- int count = chars.length;
- if (count == 0) return 0; // nothing to insert
- int position = prepareForInsert(index, count);
- for (int i = 0; i < count; i++) {
- this.chars[position + i] = chars[i];
- this.fields[position + i] = fields == null ? null : fields[i];
- }
- return count;
- }
-
- /**
- * Appends the contents of another {@link NumberStringBuilder} to the end of this instance.
- *
- * @return The number of chars added, which is the length of the other {@link
- * NumberStringBuilder}.
- */
- public int append(NumberStringBuilder other) {
- return insert(length, other);
- }
-
- /**
- * Inserts the contents of another {@link NumberStringBuilder} into this instance at the given
- * index.
- *
- * @return The number of chars added, which is the length of the other {@link
- * NumberStringBuilder}.
- */
- public int insert(int index, NumberStringBuilder other) {
- assert this != other;
- int count = other.length;
- if (count == 0) return 0; // nothing to insert
- int position = prepareForInsert(index, count);
- for (int i = 0; i < count; i++) {
- this.chars[position + i] = other.chars[other.zero + i];
- this.fields[position + i] = other.fields[other.zero + i];
- }
- return count;
- }
-
- /**
- * Shifts around existing data if necessary to make room for new characters.
- *
- * @param index The location in the string where the operation is to take place.
- * @param count The number of chars (UTF-16 code units) to be inserted at that location.
- * @return The position in the char array to insert the chars.
- */
- private int prepareForInsert(int index, int count) {
- if (index == 0 && zero - count >= 0) {
- // Append to start
- zero -= count;
- length += count;
- return zero;
- } else if (index == length && zero + length + count < chars.length) {
- // Append to end
- length += count;
- return zero + length - count;
- } else {
- // Move chars around and/or allocate more space
- return prepareForInsertHelper(index, count);
- }
- }
-
- private int prepareForInsertHelper(int index, int count) {
- // Keeping this code out of prepareForInsert() increases the speed of append operations.
- if (length + count > chars.length) {
- char[] newChars = new char[(length + count) * 2];
- Field[] newFields = new Field[(length + count) * 2];
- int newZero = newChars.length / 2 - (length + count) / 2;
- System.arraycopy(chars, zero, newChars, newZero, index);
- System.arraycopy(chars, zero + index, newChars, newZero + index + count, length - index);
- System.arraycopy(fields, zero, newFields, newZero, index);
- System.arraycopy(fields, zero + index, newFields, newZero + index + count, length - index);
- chars = newChars;
- fields = newFields;
- zero = newZero;
- length += count;
- } else {
- int newZero = chars.length / 2 - (length + count) / 2;
- System.arraycopy(chars, zero, chars, newZero, length);
- System.arraycopy(chars, newZero + index, chars, newZero + index + count, length - index);
- System.arraycopy(fields, zero, fields, newZero, length);
- System.arraycopy(fields, newZero + index, fields, newZero + index + count, length - index);
- zero = newZero;
- length += count;
- }
- return zero + index;
- }
-
- @Override
- public CharSequence subSequence(int start, int end) {
- if (start < 0 || end > length || end < start) {
- throw new IndexOutOfBoundsException();
- }
- NumberStringBuilder other = this.clone();
- other.zero = zero + start;
- other.length = end - start;
- return other;
- }
-
- /**
- * Returns the string represented by the characters in this string builder.
- *
- * <p>For a string intended be used for debugging, use {@link #toDebugString}.
- */
- @Override
- public String toString() {
- return new String(chars, zero, length);
- }
-
- private static final Map<Field, Character> fieldToDebugChar = new HashMap<Field, Character>();
-
- static {
- fieldToDebugChar.put(NumberFormat.Field.SIGN, '-');
- fieldToDebugChar.put(NumberFormat.Field.INTEGER, 'i');
- fieldToDebugChar.put(NumberFormat.Field.FRACTION, 'f');
- fieldToDebugChar.put(NumberFormat.Field.EXPONENT, 'e');
- fieldToDebugChar.put(NumberFormat.Field.EXPONENT_SIGN, '+');
- fieldToDebugChar.put(NumberFormat.Field.EXPONENT_SYMBOL, 'E');
- fieldToDebugChar.put(NumberFormat.Field.DECIMAL_SEPARATOR, '.');
- fieldToDebugChar.put(NumberFormat.Field.GROUPING_SEPARATOR, ',');
- fieldToDebugChar.put(NumberFormat.Field.PERCENT, '%');
- fieldToDebugChar.put(NumberFormat.Field.PERMILLE, '‰');
- fieldToDebugChar.put(NumberFormat.Field.CURRENCY, '$');
- }
-
- /**
- * Returns a string that includes field information, for debugging purposes.
- *
- * <p>For example, if the string is "-12.345", the debug string will be something like
- * "<NumberStringBuilder [-123.45] [-iii.ff]>"
- *
- * @return A string for debugging purposes.
- */
- public String toDebugString() {
- StringBuilder sb = new StringBuilder();
- sb.append("<NumberStringBuilder [");
- sb.append(this.toString());
- sb.append("] [");
- for (int i = zero; i < zero + length; i++) {
- if (fields[i] == null) {
- sb.append('n');
- } else {
- sb.append(fieldToDebugChar.get(fields[i]));
- }
- }
- sb.append("]>");
- return sb.toString();
- }
-
- /** @return A new array containing the contents of this string builder. */
- public char[] toCharArray() {
- return Arrays.copyOfRange(chars, zero, zero + length);
- }
-
- /** @return A new array containing the field values of this string builder. */
- public Field[] toFieldArray() {
- return Arrays.copyOfRange(fields, zero, zero + length);
- }
-
- /**
- * @return Whether the contents and field values of this string builder are equal to the given
- * chars and fields.
- * @see #toCharArray
- * @see #toFieldArray
- */
- public boolean contentEquals(char[] chars, Field[] fields) {
- if (chars.length != length) return false;
- if (fields.length != length) return false;
- for (int i = 0; i < length; i++) {
- if (this.chars[zero + i] != chars[i]) return false;
- if (this.fields[zero + i] != fields[i]) return false;
- }
- return true;
- }
-
- /**
- * @param other The instance to compare.
- * @return Whether the contents of this instance is currently equal to the given instance.
- */
- public boolean contentEquals(NumberStringBuilder other) {
- if (length != other.length) return false;
- for (int i = 0; i < length; i++) {
- if (chars[zero + i] != other.chars[other.zero + i]) return false;
- if (fields[zero + i] != other.fields[other.zero + i]) return false;
- }
- return true;
- }
-
- /**
- * Populates the given {@link FieldPosition} based on this string builder.
- *
- * @param fp The FieldPosition to populate.
- * @param offset An offset to add to the field position index; can be zero.
- */
- public void populateFieldPosition(FieldPosition fp, int offset) {
- java.text.Format.Field rawField = fp.getFieldAttribute();
-
- if (rawField == null) {
- // Backwards compatibility: read from fp.getField()
- if (fp.getField() == NumberFormat.INTEGER_FIELD) {
- rawField = NumberFormat.Field.INTEGER;
- } else if (fp.getField() == NumberFormat.FRACTION_FIELD) {
- rawField = NumberFormat.Field.FRACTION;
- } else {
- // No field is set
- return;
- }
- }
-
- if (!(rawField instanceof com.ibm.icu.text.NumberFormat.Field)) {
- throw new IllegalArgumentException(
- "You must pass an instance of com.ibm.icu.text.NumberFormat.Field as your FieldPosition attribute. You passed: "
- + rawField.getClass().toString());
- }
- /* com.ibm.icu.text.NumberFormat. */ Field field = (Field) rawField;
-
- boolean seenStart = false;
- int fractionStart = -1;
- for (int i = zero; i <= zero + length; i++) {
- Field _field = (i < zero + length) ? fields[i] : null;
- if (seenStart && field != _field) {
- // Special case: GROUPING_SEPARATOR counts as an INTEGER.
- if (field == NumberFormat.Field.INTEGER && _field == NumberFormat.Field.GROUPING_SEPARATOR)
- continue;
- fp.setEndIndex(i - zero + offset);
- break;
- } else if (!seenStart && field == _field) {
- fp.setBeginIndex(i - zero + offset);
- seenStart = true;
- }
- if (_field == NumberFormat.Field.INTEGER || _field == NumberFormat.Field.DECIMAL_SEPARATOR) {
- fractionStart = i - zero + 1;
- }
- }
-
- // Backwards compatibility: FRACTION needs to start after INTEGER if empty
- if (field == NumberFormat.Field.FRACTION && !seenStart) {
- fp.setBeginIndex(fractionStart);
- fp.setEndIndex(fractionStart);
- }
- }
-
- public AttributedCharacterIterator getIterator() {
- AttributedString as = new AttributedString(toString());
- Field current = null;
- int currentStart = -1;
- for (int i = 0; i < length; i++) {
- Field field = fields[i + zero];
- if (current == NumberFormat.Field.INTEGER && field == NumberFormat.Field.GROUPING_SEPARATOR) {
- // Special case: GROUPING_SEPARATOR counts as an INTEGER.
- as.addAttribute(
- NumberFormat.Field.GROUPING_SEPARATOR, NumberFormat.Field.GROUPING_SEPARATOR, i, i + 1);
- } else if (current != field) {
- if (current != null) {
- as.addAttribute(current, current, currentStart, i);
- }
- current = field;
- currentStart = i;
- }
- }
- if (current != null) {
- as.addAttribute(current, current, currentStart, length);
- }
- return as.getIterator();
- }
-
- @Override
- public NumberStringBuilder clone() {
- NumberStringBuilder other = new NumberStringBuilder(chars.length);
- other.zero = zero;
- other.length = length;
- System.arraycopy(chars, zero, other.chars, zero, length);
- System.arraycopy(fields, zero, other.fields, zero, length);
- return other;
- }
-
- public NumberStringBuilder clear() {
- zero = chars.length / 2;
- length = 0;
- return this;
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number;
-
-import com.ibm.icu.impl.number.Modifier.AffixModifier;
-import com.ibm.icu.impl.number.formatters.CompactDecimalFormat;
-import com.ibm.icu.impl.number.formatters.PositiveNegativeAffixFormat;
-import com.ibm.icu.impl.number.formatters.PositiveNegativeAffixFormat.IProperties;
-import com.ibm.icu.impl.number.modifiers.ConstantAffixModifier;
-import com.ibm.icu.impl.number.modifiers.ConstantMultiFieldModifier;
-import com.ibm.icu.text.DecimalFormatSymbols;
-import com.ibm.icu.text.NumberFormat.Field;
-
-/**
- * A class to convert from a bag of prefix/suffix properties into a positive and negative {@link
- * Modifier}. This is a standard implementation used by {@link PositiveNegativeAffixFormat}, {@link
- * CompactDecimalFormat}, {@link Parse}, and others.
- *
- * <p>This class is is intended to be an efficient generator for instances of Modifier by a single
- * thread during construction of a formatter or during static formatting. It uses internal caching
- * to avoid creating new Modifier objects when possible. It is NOT THREAD SAFE and NOT IMMUTABLE.
- *
- * <p>The thread-local instance of this class provided by {@link #getThreadLocalInstance} should be
- * used in most cases instead of constructing a new instance of the object.
- *
- * <p>This class also handles the logic of assigning positive signs, negative signs, and currency
- * signs according to the LDML specification.
- */
-public class PNAffixGenerator {
- public static class Result {
- public AffixModifier positive = null;
- public AffixModifier negative = null;
- }
-
- protected static final ThreadLocal<PNAffixGenerator> threadLocalInstance =
- new ThreadLocal<PNAffixGenerator>() {
- @Override
- protected PNAffixGenerator initialValue() {
- return new PNAffixGenerator();
- }
- };
-
- public static PNAffixGenerator getThreadLocalInstance() {
- return threadLocalInstance.get();
- }
-
- // These instances are used internally and cached to avoid object creation. The resultInstance
- // also serves as a 1-element cache to avoid creating objects when subsequent calls have
- // identical prefixes and suffixes. This happens, for example, when consuming CDF data.
- private Result resultInstance = new Result();
- private NumberStringBuilder sb1 = new NumberStringBuilder();
- private NumberStringBuilder sb2 = new NumberStringBuilder();
- private NumberStringBuilder sb3 = new NumberStringBuilder();
- private NumberStringBuilder sb4 = new NumberStringBuilder();
-
- /**
- * Generates modifiers using default currency symbols.
- *
- * @param symbols The symbols to interpolate for minus, plus, percent, permille, and currency.
- * @param properties The bag of properties to convert.
- * @return The positive and negative {@link Modifier}.
- */
- public Result getModifiers(
- DecimalFormatSymbols symbols, PositiveNegativeAffixFormat.IProperties properties) {
- // If this method is used, the user doesn't care about currencies. Default the currency symbols
- // to the information we can get from the DecimalFormatSymbols instance.
- return getModifiers(
- symbols,
- symbols.getCurrencySymbol(),
- symbols.getInternationalCurrencySymbol(),
- symbols.getCurrencySymbol(),
- properties);
- }
-
- /**
- * Generates modifiers using the specified currency symbol for all three lengths of currency
- * placeholders in the pattern string.
- *
- * @param symbols The symbols to interpolate for minus, plus, percent, and permille.
- * @param currencySymbol The currency symbol.
- * @param properties The bag of properties to convert.
- * @return The positive and negative {@link Modifier}.
- */
- public Result getModifiers(
- DecimalFormatSymbols symbols,
- String currencySymbol,
- PositiveNegativeAffixFormat.IProperties properties) {
- // If this method is used, the user doesn't cares about currencies but doesn't care about
- // supporting all three sizes of currency placeholders. Use the one provided string for all
- // three sizes of placeholders.
- return getModifiers(symbols, currencySymbol, currencySymbol, currencySymbol, properties);
- }
-
- /**
- * Generates modifiers using the three specified strings to replace the three lengths of currency
- * placeholders: "¤", "¤¤", and "¤¤¤".
- *
- * @param symbols The symbols to interpolate for minus, plus, percent, and permille.
- * @param curr1 The string to replace "¤".
- * @param curr2 The string to replace "¤¤".
- * @param curr3 The string to replace "¤¤¤".
- * @param properties The bag of properties to convert.
- * @return The positive and negative {@link Modifier}.
- */
- public Result getModifiers(
- DecimalFormatSymbols symbols,
- String curr1,
- String curr2,
- String curr3,
- PositiveNegativeAffixFormat.IProperties properties) {
-
- // Use a different code path for handling affixes with "always show plus sign"
- if (properties.getPlusSignAlwaysShown()) {
- return getModifiersWithPlusSign(symbols, curr1, curr2, curr3, properties);
- }
-
- CharSequence ppp = properties.getPositivePrefixPattern();
- CharSequence psp = properties.getPositiveSuffixPattern();
- CharSequence npp = properties.getNegativePrefixPattern();
- CharSequence nsp = properties.getNegativeSuffixPattern();
-
- // Set sb1/sb2 to the positive prefix/suffix.
- sb1.clear();
- sb2.clear();
- AffixPatternUtils.unescape(ppp, symbols, curr1, curr2, curr3, null, sb1);
- AffixPatternUtils.unescape(psp, symbols, curr1, curr2, curr3, null, sb2);
- setPositiveResult(sb1, sb2, properties);
-
- // Set sb1/sb2 to the negative prefix/suffix.
- if (npp == null && nsp == null) {
- // Negative prefix defaults to positive prefix prepended with the minus sign.
- // Negative suffix defaults to positive suffix.
- sb1.insert(0, symbols.getMinusSignString(), Field.SIGN);
- } else {
- sb1.clear();
- sb2.clear();
- AffixPatternUtils.unescape(npp, symbols, curr1, curr2, curr3, null, sb1);
- AffixPatternUtils.unescape(nsp, symbols, curr1, curr2, curr3, null, sb2);
- }
- setNegativeResult(sb1, sb2, properties);
-
- return resultInstance;
- }
-
- private Result getModifiersWithPlusSign(
- DecimalFormatSymbols symbols,
- String curr1,
- String curr2,
- String curr3,
- IProperties properties) {
-
- CharSequence ppp = properties.getPositivePrefixPattern();
- CharSequence psp = properties.getPositiveSuffixPattern();
- CharSequence npp = properties.getNegativePrefixPattern();
- CharSequence nsp = properties.getNegativeSuffixPattern();
-
- // There are three cases, listed below with their expected outcomes.
- // TODO: Should we handle the cases when the positive subpattern has a '+' already?
- //
- // 1) No negative subpattern.
- // Positive => Positive subpattern prepended with '+'
- // Negative => Positive subpattern prepended with '-'
- // 2) Negative subpattern does not have '-'.
- // Positive => Positive subpattern prepended with '+'
- // Negative => Negative subpattern
- // 3) Negative subpattern has '-'.
- // Positive => Negative subpattern with '+' substituted for '-'
- // Negative => Negative subpattern
-
- if (npp != null || nsp != null) {
- // Case 2 or Case 3
- sb1.clear();
- sb2.clear();
- sb3.clear();
- sb4.clear();
- AffixPatternUtils.unescape(npp, symbols, curr1, curr2, curr3, null, sb1);
- AffixPatternUtils.unescape(nsp, symbols, curr1, curr2, curr3, null, sb2);
- AffixPatternUtils.unescape(
- npp, symbols, curr1, curr2, curr3, symbols.getPlusSignString(), sb3);
- AffixPatternUtils.unescape(
- nsp, symbols, curr1, curr2, curr3, symbols.getPlusSignString(), sb4);
- if (!charSequenceEquals(sb1, sb3) || !charSequenceEquals(sb2, sb4)) {
- // Case 3. The plus sign substitution was successful.
- setPositiveResult(sb3, sb4, properties);
- setNegativeResult(sb1, sb2, properties);
- return resultInstance;
- } else {
- // Case 2. There was no minus sign. Set the negative result and fall through.
- setNegativeResult(sb1, sb2, properties);
- }
- }
-
- // Case 1 or 2. Set sb1/sb2 to the positive prefix/suffix.
- sb1.clear();
- sb2.clear();
- AffixPatternUtils.unescape(ppp, symbols, curr1, curr2, curr3, null, sb1);
- AffixPatternUtils.unescape(psp, symbols, curr1, curr2, curr3, null, sb2);
-
- if (npp == null && nsp == null) {
- // Case 1. Compute the negative result from the positive subpattern.
- sb3.clear();
- sb3.append(symbols.getMinusSignString(), Field.SIGN);
- sb3.append(sb1);
- setNegativeResult(sb3, sb2, properties);
- }
-
- // Case 1 or 2. Prepend a '+' sign to the positive prefix.
- sb1.insert(0, symbols.getPlusSignString(), Field.SIGN);
- setPositiveResult(sb1, sb2, properties);
-
- return resultInstance;
- }
-
- private void setPositiveResult(
- NumberStringBuilder prefix, NumberStringBuilder suffix, IProperties properties) {
- if (properties.getPositivePrefix() != null || properties.getPositiveSuffix() != null) {
- // Override with custom affixes
- String _prefix = properties.getPositivePrefix();
- String _suffix = properties.getPositiveSuffix();
- if (_prefix == null) _prefix = "";
- if (_suffix == null) _suffix = "";
- if (_prefix.length() == 0 && _suffix.length() == 0) {
- resultInstance.positive = ConstantAffixModifier.EMPTY;
- return;
- }
- if (resultInstance.positive != null
- && (resultInstance.positive instanceof ConstantAffixModifier)
- && ((ConstantAffixModifier) resultInstance.positive).contentEquals(_prefix, _suffix)) {
- // Use the cached modifier
- return;
- }
- resultInstance.positive =
- new ConstantAffixModifier(_prefix, _suffix, null, false);
- } else {
- // Use pattern affixes
- if (prefix.length() == 0 && suffix.length() == 0) {
- resultInstance.positive = ConstantAffixModifier.EMPTY;
- return;
- }
- if (resultInstance.positive != null
- && (resultInstance.positive instanceof ConstantMultiFieldModifier)
- && ((ConstantMultiFieldModifier) resultInstance.positive).contentEquals(prefix, suffix)) {
- // Use the cached modifier
- return;
- }
- resultInstance.positive = new ConstantMultiFieldModifier(prefix, suffix, false);
- }
- }
-
- private void setNegativeResult(
- NumberStringBuilder prefix, NumberStringBuilder suffix, IProperties properties) {
- if (properties.getNegativePrefix() != null || properties.getNegativeSuffix() != null) {
- // Override with custom affixes
- String _prefix = properties.getNegativePrefix();
- String _suffix = properties.getNegativeSuffix();
- if (_prefix == null) _prefix = "";
- if (_suffix == null) _suffix = "";
- if (_prefix.length() == 0 && _suffix.length() == 0) {
- resultInstance.negative = ConstantAffixModifier.EMPTY;
- return;
- }
- if (resultInstance.negative != null
- && (resultInstance.negative instanceof ConstantAffixModifier)
- && ((ConstantAffixModifier) resultInstance.negative).contentEquals(_prefix, _suffix)) {
- // Use the cached modifier
- return;
- }
- resultInstance.negative =
- new ConstantAffixModifier(_prefix, _suffix, null, false);
- } else {
- // Use pattern affixes
- if (prefix.length() == 0 && suffix.length() == 0) {
- resultInstance.negative = ConstantAffixModifier.EMPTY;
- return;
- }
- if (resultInstance.negative != null
- && (resultInstance.negative instanceof ConstantMultiFieldModifier)
- && ((ConstantMultiFieldModifier) resultInstance.negative).contentEquals(prefix, suffix)) {
- // Use the cached modifier
- return;
- }
- resultInstance.negative = new ConstantMultiFieldModifier(prefix, suffix, false);
- }
- }
-
- /** A null-safe equals method for CharSequences. */
- private static boolean charSequenceEquals(CharSequence a, CharSequence b) {
- if (a == b) return true;
- if (a == null || b == null) return false;
- if (a.length() != b.length()) return false;
- for (int i = 0; i < a.length(); i++) {
- if (a.charAt(i) != b.charAt(i)) return false;
- }
- return true;
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number;
-
-import java.math.BigDecimal;
-import java.math.MathContext;
-import java.text.ParseException;
-import java.text.ParsePosition;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-
-import com.ibm.icu.impl.StandardPlural;
-import com.ibm.icu.impl.TextTrieMap;
-import com.ibm.icu.impl.number.Parse.ParseMode;
-import com.ibm.icu.impl.number.formatters.BigDecimalMultiplier;
-import com.ibm.icu.impl.number.formatters.CurrencyFormat;
-import com.ibm.icu.impl.number.formatters.MagnitudeMultiplier;
-import com.ibm.icu.impl.number.formatters.PaddingFormat;
-import com.ibm.icu.impl.number.formatters.PositiveDecimalFormat;
-import com.ibm.icu.impl.number.formatters.PositiveNegativeAffixFormat;
-import com.ibm.icu.lang.UCharacter;
-import com.ibm.icu.text.CurrencyPluralInfo;
-import com.ibm.icu.text.DecimalFormatSymbols;
-import com.ibm.icu.text.NumberFormat;
-import com.ibm.icu.text.UnicodeSet;
-import com.ibm.icu.util.Currency;
-import com.ibm.icu.util.Currency.CurrencyStringInfo;
-import com.ibm.icu.util.CurrencyAmount;
-import com.ibm.icu.util.ULocale;
-
-/**
- * A parser designed to convert an arbitrary human-generated string to its best representation as a
- * number: a long, a BigInteger, or a BigDecimal.
- *
- * <p>The parser may traverse multiple parse paths in the same strings if there is ambiguity. For
- * example, the string "12,345.67" has two main interpretations: it could be "12.345" in a locale
- * that uses '.' as the grouping separator, or it could be "12345.67" in a locale that uses ',' as
- * the grouping separator. Since the second option has a longer parse path (consumes more of the
- * input string), the parser will accept the second option.
- */
-public class Parse {
-
- /** Controls the set of rules for parsing a string. */
- public static enum ParseMode {
- /**
- * Lenient mode should be used if you want to accept malformed user input. It will use
- * heuristics to attempt to parse through typographical errors in the string.
- */
- LENIENT,
-
- /**
- * Strict mode should be used if you want to require that the input is well-formed. More
- * specifically, it differs from lenient mode in the following ways:
- *
- * <ul>
- * <li>Grouping widths must match the grouping settings. For example, "12,3,45" will fail if
- * the grouping width is 3, as in the pattern "#,##0".
- * <li>The string must contain a complete prefix and suffix. For example, if the pattern is
- * "{#};(#)", then "{123}" or "(123)" would match, but "{123", "123}", and "123" would all
- * fail. (The latter strings would be accepted in lenient mode.)
- * <li>Whitespace may not appear at arbitrary places in the string. In lenient mode,
- * whitespace is allowed to occur arbitrarily before and after prefixes and exponent
- * separators.
- * <li>Leading grouping separators are not allowed, as in ",123".
- * <li>Minus and plus signs can only appear if specified in the pattern. In lenient mode, a
- * plus or minus sign can always precede a number.
- * <li>The set of characters that can be interpreted as a decimal or grouping separator is
- * smaller.
- * <li><strong>If currency parsing is enabled,</strong> currencies must only appear where
- * specified in either the current pattern string or in a valid pattern string for the
- * current locale. For example, if the pattern is "¤0.00", then "$1.23" would match, but
- * "1.23$" would fail to match.
- * </ul>
- */
- STRICT,
-
- /**
- * Fast mode should be used in applications that don't require prefixes and suffixes to match.
- *
- * <p>In addition to ignoring prefixes and suffixes, fast mode performs the following
- * optimizations:
- *
- * <ul>
- * <li>Ignores digit strings from {@link DecimalFormatSymbols} and only uses the code point's
- * Unicode digit property. If you are not using custom digit strings, this should not
- * cause a change in behavior.
- * <li>Instead of traversing multiple possible parse paths, a "greedy" parsing strategy is
- * used, which might mean that fast mode won't accept strings that lenient or strict mode
- * would accept. Since prefix and suffix strings are ignored, this is not an issue unless
- * you are using custom symbols.
- * </ul>
- */
- FAST,
- }
-
- /** The set of properties required for {@link Parse}. Accepts a {@link Properties} object. */
- public static interface IProperties
- extends PositiveNegativeAffixFormat.IProperties,
- PaddingFormat.IProperties,
- CurrencyFormat.ICurrencyProperties,
- BigDecimalMultiplier.IProperties,
- MagnitudeMultiplier.IProperties,
- PositiveDecimalFormat.IProperties {
-
- boolean DEFAULT_PARSE_INTEGER_ONLY = false;
-
- /** @see #setParseIntegerOnly */
- public boolean getParseIntegerOnly();
-
- /**
- * Whether to ignore the fractional part of numbers. For example, parses "123.4" to "123"
- * instead of "123.4".
- *
- * @param parseIntegerOnly true to parse integers only; false to parse integers with their
- * fraction parts
- * @return The property bag, for chaining.
- */
- public IProperties setParseIntegerOnly(boolean parseIntegerOnly);
-
- boolean DEFAULT_PARSE_NO_EXPONENT = false;
-
- /** @see #setParseNoExponent */
- public boolean getParseNoExponent();
-
- /**
- * Whether to ignore the exponential part of numbers. For example, parses "123E4" to "123"
- * instead of "1230000".
- *
- * @param parseIgnoreExponent true to ignore exponents; false to parse them.
- * @return The property bag, for chaining.
- */
- public IProperties setParseNoExponent(boolean parseIgnoreExponent);
-
- boolean DEFAULT_DECIMAL_PATTERN_MATCH_REQUIRED = false;
-
- /** @see #setDecimalPatternMatchRequired */
- public boolean getDecimalPatternMatchRequired();
-
- /**
- * Whether to require that the presence of decimal point matches the pattern. If a decimal point
- * is not present, but the pattern contained a decimal point, parse will not succeed: null will
- * be returned from <code>parse()</code>, and an error index will be set in the {@link
- * ParsePosition}.
- *
- * @param decimalPatternMatchRequired true to set an error if decimal is not present
- * @return The property bag, for chaining.
- */
- public IProperties setDecimalPatternMatchRequired(boolean decimalPatternMatchRequired);
-
- ParseMode DEFAULT_PARSE_MODE = null;
-
- /** @see #setParseMode */
- public ParseMode getParseMode();
-
- /**
- * Controls certain rules for how strict this parser is when reading strings. See {@link
- * ParseMode#LENIENT} and {@link ParseMode#STRICT}.
- *
- * @param parseMode Either {@link ParseMode#LENIENT} or {@link ParseMode#STRICT}.
- * @return The property bag, for chaining.
- */
- public IProperties setParseMode(ParseMode parseMode);
-
- boolean DEFAULT_PARSE_TO_BIG_DECIMAL = false;
-
- /** @see #setParseToBigDecimal */
- public boolean getParseToBigDecimal();
-
- /**
- * Whether to always return a BigDecimal from {@link Parse#parse} and all other parse methods.
- * By default, a Long or a BigInteger are returned when possible.
- *
- * @param parseToBigDecimal true to always return a BigDecimal; false to return a Long or a
- * BigInteger when possible.
- * @return The property bag, for chaining.
- */
- public IProperties setParseToBigDecimal(boolean parseToBigDecimal);
-
- boolean DEFAULT_PARSE_CASE_SENSITIVE = false;
-
- /** @see #setParseCaseSensitive */
- public boolean getParseCaseSensitive();
-
- /**
- * Whether to require cases to match when parsing strings; default is true. Case sensitivity
- * applies to prefixes, suffixes, the exponent separator, the symbol "NaN", and the infinity
- * symbol. Grouping separators, decimal separators, and padding are always case-sensitive.
- * Currencies are always case-insensitive.
- *
- * <p>This setting is ignored in fast mode. In fast mode, strings are always compared in a
- * case-sensitive way.
- *
- * @param parseCaseSensitive true to be case-sensitive when parsing; false to allow any case.
- * @return The property bag, for chaining.
- */
- public IProperties setParseCaseSensitive(boolean parseCaseSensitive);
- }
-
- /**
- * @see #parse(String, ParsePosition, ParseMode, boolean, boolean, IProperties,
- * DecimalFormatSymbols)
- */
- private static enum StateName {
- BEFORE_PREFIX,
- AFTER_PREFIX,
- AFTER_INTEGER_DIGIT,
- AFTER_FRACTION_DIGIT,
- AFTER_EXPONENT_SEPARATOR,
- AFTER_EXPONENT_DIGIT,
- BEFORE_SUFFIX,
- BEFORE_SUFFIX_SEEN_EXPONENT,
- AFTER_SUFFIX,
- INSIDE_CURRENCY,
- INSIDE_DIGIT,
- INSIDE_STRING,
- INSIDE_AFFIX_PATTERN;
- }
-
- // TODO: Does this set make sense for the whitespace characters?
- private static final UnicodeSet UNISET_WHITESPACE =
- new UnicodeSet("[[:whitespace:][\\u2000-\\u200D]]").freeze();
-
- // BiDi characters are skipped over and ignored at any point in the string, even in strict mode.
- private static final UnicodeSet UNISET_BIDI =
- new UnicodeSet("[[\\u200E\\u200F\\u061C]]").freeze();
-
- // TODO: Re-generate these sets from the database. They probably haven't been updated in a while.
- private static final UnicodeSet UNISET_PERIOD_LIKE =
- new UnicodeSet("[.\\u2024\\u3002\\uFE12\\uFE52\\uFF0E\\uFF61]").freeze();
- private static final UnicodeSet UNISET_STRICT_PERIOD_LIKE =
- new UnicodeSet("[.\\u2024\\uFE52\\uFF0E\\uFF61]").freeze();
- private static final UnicodeSet UNISET_COMMA_LIKE =
- new UnicodeSet("[,\\u060C\\u066B\\u3001\\uFE10\\uFE11\\uFE50\\uFE51\\uFF0C\\uFF64]").freeze();
- private static final UnicodeSet UNISET_STRICT_COMMA_LIKE =
- new UnicodeSet("[,\\u066B\\uFE10\\uFE50\\uFF0C]").freeze();
- private static final UnicodeSet UNISET_OTHER_GROUPING_SEPARATORS =
- new UnicodeSet(
- "[\\ '\\u00A0\\u066C\\u2000-\\u200A\\u2018\\u2019\\u202F\\u205F\\u3000\\uFF07]")
- .freeze();
-
- private enum SeparatorType {
- COMMA_LIKE,
- PERIOD_LIKE,
- OTHER_GROUPING,
- UNKNOWN;
-
- static SeparatorType fromCp(int cp, ParseMode mode) {
- if (mode == ParseMode.FAST) {
- return SeparatorType.UNKNOWN;
- } else if (mode == ParseMode.STRICT) {
- if (UNISET_STRICT_COMMA_LIKE.contains(cp)) return COMMA_LIKE;
- if (UNISET_STRICT_PERIOD_LIKE.contains(cp)) return PERIOD_LIKE;
- if (UNISET_OTHER_GROUPING_SEPARATORS.contains(cp)) return OTHER_GROUPING;
- return UNKNOWN;
- } else {
- if (UNISET_COMMA_LIKE.contains(cp)) return COMMA_LIKE;
- if (UNISET_PERIOD_LIKE.contains(cp)) return PERIOD_LIKE;
- if (UNISET_OTHER_GROUPING_SEPARATORS.contains(cp)) return OTHER_GROUPING;
- return UNKNOWN;
- }
- }
- }
-
- private static enum DigitType {
- INTEGER,
- FRACTION,
- EXPONENT
- }
-
- /**
- * Holds a snapshot in time of a single parse path. This includes the digits seen so far, the
- * current state name, and other properties like the grouping separator used on this parse path,
- * details about the exponent and negative signs, etc.
- */
- private static class StateItem {
- // Parser state:
- // The "trailingChars" is used to keep track of how many characters from the end of the string
- // are ignorable and should be removed from the parse position should this item be accepted.
- // The "score" is used to help rank two otherwise equivalent parse paths. Currently, the only
- // function giving points to the score is prefix/suffix.
- StateName name;
- int trailingCount;
- int score;
-
- // Numerical value:
- FormatQuantity4 fq = new FormatQuantity4();
- int numDigits;
- int trailingZeros;
- int exponent;
-
- // Other items that we've seen:
- int groupingCp;
- long groupingWidths;
- String isoCode;
- boolean sawNegative;
- boolean sawNegativeExponent;
- boolean sawCurrency;
- boolean sawNaN;
- boolean sawInfinity;
- AffixHolder affix;
- boolean sawPrefix;
- boolean sawSuffix;
- boolean sawDecimalPoint;
-
- // Data for intermediate parsing steps:
- StateName returnTo1;
- StateName returnTo2;
- // For string literals:
- CharSequence currentString;
- int currentOffset;
- // For affix patterns:
- CharSequence currentAffixPattern;
- long currentStepwiseParserTag;
- // For currency:
- TextTrieMap<CurrencyStringInfo>.ParseState currentCurrencyTrieState;
- // For multi-code-point digits:
- TextTrieMap<Byte>.ParseState currentDigitTrieState;
- DigitType currentDigitType;
-
- /**
- * Clears the instance so that it can be re-used.
- *
- * @return Myself, for chaining.
- */
- StateItem clear() {
- // Parser state:
- name = StateName.BEFORE_PREFIX;
- trailingCount = 0;
- score = 0;
-
- // Numerical value:
- fq.clear();
- numDigits = 0;
- trailingZeros = 0;
- exponent = 0;
-
- // Other items we've seen:
- groupingCp = -1;
- groupingWidths = 0L;
- isoCode = null;
- sawNegative = false;
- sawNegativeExponent = false;
- sawCurrency = false;
- sawNaN = false;
- sawInfinity = false;
- affix = null;
- sawPrefix = false;
- sawSuffix = false;
- sawDecimalPoint = false;
-
- // Data for intermediate parsing steps:
- returnTo1 = null;
- returnTo2 = null;
- currentString = null;
- currentOffset = 0;
- currentAffixPattern = null;
- currentStepwiseParserTag = 0L;
- currentCurrencyTrieState = null;
- currentDigitTrieState = null;
- currentDigitType = null;
-
- return this;
- }
-
- /**
- * Sets the internal value of this instance equal to another instance.
- *
- * <p>newName and cpOrN1 are required as parameters to this function because every time a code
- * point is consumed and a state item is copied, both of the corresponding fields should be
- * updated; it would be an error if they weren't updated.
- *
- * @param other The instance to copy from.
- * @param newName The state name that the new copy should take on.
- * @param trailing If positive, record this code point as trailing; if negative, reset the
- * trailing count to zero.
- * @return Myself, for chaining.
- */
- StateItem copyFrom(StateItem other, StateName newName, int trailing) {
- // Parser state:
- name = newName;
- score = other.score;
-
- // Either reset trailingCount or add the width of the current code point.
- trailingCount = (trailing < 0) ? 0 : other.trailingCount + Character.charCount(trailing);
-
- // Numerical value:
- fq.copyFrom(other.fq);
- numDigits = other.numDigits;
- trailingZeros = other.trailingZeros;
- exponent = other.exponent;
-
- // Other items we've seen:
- groupingCp = other.groupingCp;
- groupingWidths = other.groupingWidths;
- isoCode = other.isoCode;
- sawNegative = other.sawNegative;
- sawNegativeExponent = other.sawNegativeExponent;
- sawCurrency = other.sawCurrency;
- sawNaN = other.sawNaN;
- sawInfinity = other.sawInfinity;
- affix = other.affix;
- sawPrefix = other.sawPrefix;
- sawSuffix = other.sawSuffix;
- sawDecimalPoint = other.sawDecimalPoint;
-
- // Data for intermediate parsing steps:
- returnTo1 = other.returnTo1;
- returnTo2 = other.returnTo2;
- currentString = other.currentString;
- currentOffset = other.currentOffset;
- currentAffixPattern = other.currentAffixPattern;
- currentStepwiseParserTag = other.currentStepwiseParserTag;
- currentCurrencyTrieState = other.currentCurrencyTrieState;
- currentDigitTrieState = other.currentDigitTrieState;
- currentDigitType = other.currentDigitType;
-
- return this;
- }
-
- /**
- * Adds a digit to the internal representation of this instance.
- *
- * @param digit The digit that was read from the string.
- * @param type Whether the digit occured after the decimal point.
- */
- void appendDigit(byte digit, DigitType type) {
- if (type == DigitType.EXPONENT) {
- int newExponent = exponent * 10 + digit;
- if (newExponent < exponent) {
- // overflow
- exponent = Integer.MAX_VALUE;
- } else {
- exponent = newExponent;
- }
- } else {
- numDigits++;
- if (type == DigitType.FRACTION && digit == 0) {
- trailingZeros++;
- } else if (type == DigitType.FRACTION) {
- fq.appendDigit(digit, trailingZeros, false);
- trailingZeros = 0;
- } else {
- fq.appendDigit(digit, 0, true);
- }
- }
- }
-
- /** @return Whether or not this item contains a valid number. */
- public boolean hasNumber() {
- return numDigits > 0 || sawNaN || sawInfinity;
- }
-
- /**
- * Converts the internal digits from this instance into a Number, preferring a Long, then a
- * BigInteger, then a BigDecimal. A Double is used for NaN, infinity, and -0.0.
- *
- * @return The Number. Never null.
- */
- Number toNumber(IProperties properties) {
- // Check for NaN, infinity, and -0.0
- if (sawNaN) {
- return Double.NaN;
- }
- if (sawInfinity) {
- if (sawNegative) {
- return Double.NEGATIVE_INFINITY;
- } else {
- return Double.POSITIVE_INFINITY;
- }
- }
- if (fq.isZero() && sawNegative) {
- return -0.0;
- }
-
- // Check for exponent overflow
- boolean forceBigDecimal = properties.getParseToBigDecimal();
- if (exponent == Integer.MAX_VALUE) {
- if (sawNegativeExponent && sawNegative) {
- return -0.0;
- } else if (sawNegativeExponent) {
- return 0.0;
- } else if (sawNegative) {
- return Double.NEGATIVE_INFINITY;
- } else {
- return Double.POSITIVE_INFINITY;
- }
- } else if (exponent > 1000) {
- // BigDecimals can handle huge values better than BigIntegers.
- forceBigDecimal = true;
- }
-
- // Multipliers must be applied in reverse.
- BigDecimal multiplier = properties.getMultiplier();
- if (properties.getMagnitudeMultiplier() != 0) {
- if (multiplier == null) multiplier = BigDecimal.ONE;
- multiplier = multiplier.scaleByPowerOfTen(properties.getMagnitudeMultiplier());
- }
- int delta = (sawNegativeExponent ? -1 : 1) * exponent;
-
- // We need to use a math context in order to prevent non-terminating decimal expansions.
- // This is only used when dividing by the multiplier.
- MathContext mc = RoundingUtils.getMathContextOr16Digits(properties);
-
- // Construct the output number.
- // This is the only step during fast-mode parsing that incurs object creations.
- BigDecimal result = fq.toBigDecimal();
- if (sawNegative) result = result.negate();
- result = result.scaleByPowerOfTen(delta);
- if (multiplier != null) {
- result = result.divide(multiplier, mc);
- }
- result = result.stripTrailingZeros();
- if (forceBigDecimal || result.scale() > 0) {
- return result;
- } else if (-result.scale() + result.precision() <= 18) {
- return result.longValueExact();
- } else {
- return result.toBigIntegerExact();
- }
- }
-
- /**
- * Converts the internal digits to a number, and also associates the number with the parsed
- * currency.
- *
- * @return The CurrencyAmount. Never null.
- */
- public CurrencyAmount toCurrencyAmount(IProperties properties) {
- assert isoCode != null;
- Number number = toNumber(properties);
- Currency currency = Currency.getInstance(isoCode);
- return new CurrencyAmount(number, currency);
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append("<ParserStateItem ");
- sb.append(name.name());
- if (name == StateName.INSIDE_STRING) {
- sb.append("{");
- sb.append(currentString);
- sb.append(":");
- sb.append(currentOffset);
- sb.append("}");
- }
- if (name == StateName.INSIDE_AFFIX_PATTERN) {
- sb.append("{");
- sb.append(currentAffixPattern);
- sb.append(":");
- sb.append(AffixPatternUtils.getOffset(currentStepwiseParserTag));
- sb.append("}");
- }
- sb.append(" ");
- sb.append(fq.toBigDecimal());
- sb.append(" grouping:");
- sb.append(groupingCp == -1 ? new char[] {'?'} : Character.toChars(groupingCp));
- sb.append(" widths:");
- sb.append(Long.toHexString(groupingWidths));
- sb.append(" seen:");
- sb.append(sawNegative ? 1 : 0);
- sb.append(sawNegativeExponent ? 1 : 0);
- sb.append(sawNaN ? 1 : 0);
- sb.append(sawInfinity ? 1 : 0);
- sb.append(sawPrefix ? 1 : 0);
- sb.append(sawSuffix ? 1 : 0);
- sb.append(sawDecimalPoint ? 1 : 0);
- sb.append(" score:");
- sb.append(score);
- sb.append(" affix:");
- sb.append(affix);
- sb.append(" currency:");
- sb.append(isoCode);
- sb.append(">");
- return sb.toString();
- }
- }
-
- /**
- * Holds an ordered list of {@link StateItem} and other metadata about the string to be parsed.
- * There are two internal arrays of {@link StateItem}, which are swapped back and forth in order
- * to avoid object creations. The items in one array can be populated at the same time that items
- * in the other array are being read from.
- */
- private static class ParserState {
-
- // Basic ParserStateItem lists:
- StateItem[] items = new StateItem[16];
- StateItem[] prevItems = new StateItem[16];
- int length;
- int prevLength;
-
- // Properties and Symbols memory:
- IProperties properties;
- DecimalFormatSymbols symbols;
- ParseMode mode;
- boolean caseSensitive;
- boolean parseCurrency;
-
- // Other pre-computed fields:
- int decimalCp1;
- int decimalCp2;
- int groupingCp1;
- int groupingCp2;
- SeparatorType decimalType1;
- SeparatorType decimalType2;
- SeparatorType groupingType1;
- SeparatorType groupingType2;
- TextTrieMap<Byte> digitTrie;
- Set<AffixHolder> affixHolders = new HashSet<AffixHolder>();
-
- ParserState() {
- for (int i = 0; i < items.length; i++) {
- items[i] = new StateItem();
- prevItems[i] = new StateItem();
- }
- }
-
- /**
- * Clears the internal state in order to prepare for parsing a new string.
- *
- * @return Myself, for chaining.
- */
- ParserState clear() {
- length = 0;
- prevLength = 0;
- digitTrie = null;
- affixHolders.clear();
- return this;
- }
-
- /**
- * Swaps the internal arrays of {@link StateItem}. Sets the length of the primary list to zero,
- * so that it can be appended to.
- */
- void swap() {
- StateItem[] temp = prevItems;
- prevItems = items;
- items = temp;
- prevLength = length;
- length = 0;
- }
-
- /**
- * Swaps the internal arrays of {@link StateItem}. Sets the length of the primary list to the
- * length of the previous list, so that it can be read from.
- */
- void swapBack() {
- StateItem[] temp = prevItems;
- prevItems = items;
- items = temp;
- length = prevLength;
- prevLength = 0;
- }
-
- /**
- * Gets the next available {@link StateItem} from the primary list for writing. This method
- * should be thought of like a list append method, except that there are no object creations
- * taking place.
- *
- * <p>It is the caller's responsibility to call either {@link StateItem#clear} or {@link
- * StateItem#copyFrom} on the returned object.
- *
- * @return A dirty {@link StateItem}.
- */
- StateItem getNext() {
- if (length >= items.length) {
- // TODO: What to do here? Expand the array?
- // This case is rare and would happen only with specially designed input.
- // For now, just overwrite the last entry.
- length = items.length - 1;
- }
- StateItem item = items[length];
- length++;
- return item;
- }
-
- /** @return The index of the last inserted StateItem via a call to {@link #getNext}. */
- public int lastInsertedIndex() {
- assert length > 0;
- return length - 1;
- }
-
- /**
- * Gets a {@link StateItem} from the primary list. Assumes that the item has already been added
- * via a call to {@link #getNext}.
- *
- * @param i The index of the item to get.
- * @return The item.
- */
- public StateItem getItem(int i) {
- assert i >= 0 && i < length;
- return items[i];
- }
- }
-
- private static class AffixHolder {
- final String p; // prefix
- final String s; // suffix
- final boolean strings;
- final boolean negative;
-
- static final AffixHolder EMPTY_POSITIVE = new AffixHolder("", "", true, false);
- static final AffixHolder EMPTY_NEGATIVE = new AffixHolder("", "", true, true);
- static final AffixHolder DEFAULT_POSITIVE = new AffixHolder("+", "", false, false);
- static final AffixHolder DEFAULT_NEGATIVE = new AffixHolder("-", "", false, true);
-
- static void addToState(ParserState state, IProperties properties) {
- AffixHolder pp = fromPropertiesPositivePattern(properties);
- AffixHolder np = fromPropertiesNegativePattern(properties);
- AffixHolder ps = fromPropertiesPositiveString(properties);
- AffixHolder ns = fromPropertiesNegativeString(properties);
- if (pp == null && ps == null) {
- if (properties.getPlusSignAlwaysShown()) {
- state.affixHolders.add(DEFAULT_POSITIVE);
- } else {
- state.affixHolders.add(EMPTY_POSITIVE);
- }
- } else {
- if (pp != null) state.affixHolders.add(pp);
- if (ps != null) state.affixHolders.add(ps);
- }
- if (np == null && ns == null) {
- state.affixHolders.add(DEFAULT_NEGATIVE);
- } else {
- if (np != null) state.affixHolders.add(np);
- if (ns != null) state.affixHolders.add(ns);
- }
- }
-
- static AffixHolder fromPropertiesPositivePattern(IProperties properties) {
- String ppp = properties.getPositivePrefixPattern();
- String psp = properties.getPositiveSuffixPattern();
- return getInstance(ppp, psp, false, false);
- }
-
- static AffixHolder fromPropertiesNegativePattern(IProperties properties) {
- String npp = properties.getNegativePrefixPattern();
- String nsp = properties.getNegativeSuffixPattern();
- return getInstance(npp, nsp, false, true);
- }
-
- static AffixHolder fromPropertiesPositiveString(IProperties properties) {
- String pp = properties.getPositivePrefix();
- String ps = properties.getPositiveSuffix();
- return getInstance(pp, ps, true, false);
- }
-
- static AffixHolder fromPropertiesNegativeString(IProperties properties) {
- String np = properties.getNegativePrefix();
- String ns = properties.getNegativeSuffix();
- return getInstance(np, ns, true, true);
- }
-
- static AffixHolder getInstance(String p, String s, boolean strings, boolean negative) {
- if (p == null && s == null) return null;
- if (p == null) p = "";
- if (s == null) s = "";
- if (p.length() == 0 && s.length() == 0) return negative ? EMPTY_NEGATIVE : EMPTY_POSITIVE;
- return new AffixHolder(p, s, strings, negative);
- }
-
- AffixHolder(String pp, String sp, boolean strings, boolean negative) {
- this.p = pp;
- this.s = sp;
- this.strings = strings;
- this.negative = negative;
- }
-
- @Override
- public boolean equals(Object other) {
- if (other == null) return false;
- if (this == other) return true;
- if (!(other instanceof AffixHolder)) return false;
- AffixHolder _other = (AffixHolder) other;
- if (!p.equals(_other.p)) return false;
- if (!s.equals(_other.s)) return false;
- if (strings != _other.strings) return false;
- if (negative != _other.negative) return false;
- return true;
- }
-
- @Override
- public int hashCode() {
- return p.hashCode() ^ s.hashCode();
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append("{");
- sb.append(p);
- sb.append("|");
- sb.append(s);
- sb.append("|");
- sb.append(strings ? 'S' : 'P');
- sb.append("}");
- return sb.toString();
- }
- }
-
- /**
- * A class that holds information about all currency affix patterns for the locale. This allows
- * the parser to accept currencies in any format that are valid for the locale.
- */
- private static class CurrencyAffixPatterns {
- private final Set<AffixHolder> set = new HashSet<AffixHolder>();
-
- private static final ConcurrentHashMap<ULocale, CurrencyAffixPatterns> currencyAffixPatterns =
- new ConcurrentHashMap<ULocale, CurrencyAffixPatterns>();
-
- static void addToState(ULocale uloc, ParserState state) {
- if (!currencyAffixPatterns.containsKey(uloc)) {
- // There can be multiple threads computing the same CurrencyAffixPatterns simultaneously,
- // but that scenario is harmless.
- CurrencyAffixPatterns value = new CurrencyAffixPatterns(uloc);
- currencyAffixPatterns.put(uloc, value);
- }
- CurrencyAffixPatterns instance = currencyAffixPatterns.get(uloc);
- state.affixHolders.addAll(instance.set);
- }
-
- private CurrencyAffixPatterns(ULocale uloc) {
- // Get the basic currency pattern.
- String pattern = NumberFormat.getPattern(uloc, NumberFormat.CURRENCYSTYLE);
- addPattern(pattern);
-
- // Get the currency plural patterns.
- // TODO: Update this after CurrencyPluralInfo is replaced.
- CurrencyPluralInfo pluralInfo = CurrencyPluralInfo.getInstance(uloc);
- for (StandardPlural plural : StandardPlural.VALUES) {
- pattern = pluralInfo.getCurrencyPluralPattern(plural.getKeyword());
- addPattern(pattern);
- }
- }
-
- private static final ThreadLocal<Properties> threadLocalProperties =
- new ThreadLocal<Properties>() {
- @Override
- protected Properties initialValue() {
- return new Properties();
- }
- };
-
- private void addPattern(String pattern) {
- Properties properties = threadLocalProperties.get();
- try {
- PatternString.parseToExistingProperties(pattern, properties);
- } catch (IllegalArgumentException e) {
- // This should only happen if there is a bug in CLDR data. Fail silently.
- }
- set.add(AffixHolder.fromPropertiesPositivePattern(properties));
- set.add(AffixHolder.fromPropertiesNegativePattern(properties));
- }
- }
-
- /**
- * Makes a {@link TextTrieMap} for parsing digit strings. A trie is required only if the digit
- * strings are longer than one code point. In order for this to be the case, the user would have
- * needed to specify custom multi-character digits, like "(0)".
- *
- * @param digitStrings The list of digit strings from DecimalFormatSymbols.
- * @return A trie, or null if a trie is not required.
- */
- static TextTrieMap<Byte> makeDigitTrie(String[] digitStrings) {
- boolean requiresTrie = false;
- for (int i = 0; i < 10; i++) {
- String str = digitStrings[i];
- if (Character.charCount(Character.codePointAt(str, 0)) != str.length()) {
- requiresTrie = true;
- break;
- }
- }
- if (!requiresTrie) return null;
-
- TextTrieMap<Byte> trieMap = new TextTrieMap<Byte>(false);
- for (int i = 0; i < 10; i++) {
- trieMap.put(digitStrings[i], (byte) i);
- }
- return trieMap;
- }
-
- protected static final ThreadLocal<ParserState> threadLocalParseState =
- new ThreadLocal<ParserState>() {
- @Override
- protected ParserState initialValue() {
- return new ParserState();
- }
- };
-
- protected static final ThreadLocal<ParsePosition> threadLocalParsePosition =
- new ThreadLocal<ParsePosition>() {
- @Override
- protected ParsePosition initialValue() {
- return new ParsePosition(0);
- }
- };
-
- /**
- * @internal
- * @deprecated This API is ICU internal only. TODO: Remove this set from ScientificNumberFormat.
- */
- @Deprecated
- public static final UnicodeSet UNISET_PLUS =
- new UnicodeSet(
- 0x002B, 0x002B, 0x207A, 0x207A, 0x208A, 0x208A, 0x2795, 0x2795, 0xFB29, 0xFB29,
- 0xFE62, 0xFE62, 0xFF0B, 0xFF0B)
- .freeze();
-
- /**
- * @internal
- * @deprecated This API is ICU internal only. TODO: Remove this set from ScientificNumberFormat.
- */
- @Deprecated
- public static final UnicodeSet UNISET_MINUS =
- new UnicodeSet(
- 0x002D, 0x002D, 0x207B, 0x207B, 0x208B, 0x208B, 0x2212, 0x2212, 0x2796, 0x2796,
- 0xFE63, 0xFE63, 0xFF0D, 0xFF0D)
- .freeze();
-
- public static Number parse(String input, IProperties properties, DecimalFormatSymbols symbols) {
- ParsePosition ppos = threadLocalParsePosition.get();
- ppos.setIndex(0);
- return parse(input, ppos, properties, symbols);
- }
-
- // TODO: DELETE ME once debugging is finished
- public static volatile boolean DEBUGGING = false;
-
- /**
- * Implements an iterative parser that maintains a lists of possible states at each code point in
- * the string. At each code point in the string, the list of possible states is updated based on
- * the states coming from the previous code point. The parser stops when it reaches the end of the
- * string or when there are no possible parse paths remaining in the string.
- *
- * <p>TODO: This API is not fully flushed out. Right now this is internal-only.
- *
- * @param input The string to parse.
- * @param ppos A {@link ParsePosition} to hold the index at which parsing stopped.
- * @param properties A property bag, used only for determining the prefix/suffix strings and the
- * padding character.
- * @param symbols A {@link DecimalFormatSymbols} object, used for determining locale-specific
- * symbols for grouping/decimal separators, digit strings, and prefix/suffix substitutions.
- * @return A Number matching the parser's best interpretation of the string.
- */
- public static Number parse(
- CharSequence input,
- ParsePosition ppos,
- IProperties properties,
- DecimalFormatSymbols symbols) {
- StateItem best = _parse(input, ppos, false, properties, symbols);
- return (best == null) ? null : best.toNumber(properties);
- }
-
- public static CurrencyAmount parseCurrency(
- String input, IProperties properties, DecimalFormatSymbols symbols) throws ParseException {
- return parseCurrency(input, null, properties, symbols);
- }
-
- public static CurrencyAmount parseCurrency(
- CharSequence input, ParsePosition ppos, IProperties properties, DecimalFormatSymbols symbols)
- throws ParseException {
- if (ppos == null) {
- ppos = threadLocalParsePosition.get();
- ppos.setIndex(0);
- ppos.setErrorIndex(-1);
- }
- StateItem best = _parse(input, ppos, true, properties, symbols);
- return (best == null) ? null : best.toCurrencyAmount(properties);
- }
-
- private static StateItem _parse(
- CharSequence input,
- ParsePosition ppos,
- boolean parseCurrency,
- IProperties properties,
- DecimalFormatSymbols symbols) {
-
- if (input == null || ppos == null || properties == null || symbols == null) {
- throw new IllegalArgumentException("All arguments are required for parse.");
- }
-
- ParseMode mode = properties.getParseMode();
- if (mode == null) mode = ParseMode.LENIENT;
- boolean integerOnly = properties.getParseIntegerOnly();
- boolean ignoreExponent = properties.getParseNoExponent();
-
- // Set up the initial state
- ParserState state = threadLocalParseState.get().clear();
- state.properties = properties;
- state.symbols = symbols;
- state.mode = mode;
- state.parseCurrency = parseCurrency;
- state.caseSensitive = properties.getParseCaseSensitive();
- state.decimalCp1 = Character.codePointAt(symbols.getDecimalSeparatorString(), 0);
- state.decimalCp2 = Character.codePointAt(symbols.getMonetaryDecimalSeparatorString(), 0);
- state.groupingCp1 = Character.codePointAt(symbols.getGroupingSeparatorString(), 0);
- state.groupingCp2 = Character.codePointAt(symbols.getMonetaryGroupingSeparatorString(), 0);
- state.decimalType1 = SeparatorType.fromCp(state.decimalCp1, mode);
- state.decimalType2 = SeparatorType.fromCp(state.decimalCp2, mode);
- state.groupingType1 = SeparatorType.fromCp(state.groupingCp1, mode);
- state.groupingType2 = SeparatorType.fromCp(state.groupingCp2, mode);
- StateItem initialStateItem = state.getNext().clear();
- initialStateItem.name = StateName.BEFORE_PREFIX;
-
- if (mode == ParseMode.LENIENT || mode == ParseMode.STRICT) {
- state.digitTrie = makeDigitTrie(symbols.getDigitStringsLocal());
- AffixHolder.addToState(state, properties);
- if (parseCurrency) {
- CurrencyAffixPatterns.addToState(symbols.getULocale(), state);
- }
- }
-
- if (DEBUGGING) {
- System.out.println("Parsing: " + input);
- System.out.println(properties);
- System.out.println(state.affixHolders);
- }
-
- // Start walking through the string, one codepoint at a time. Backtracking is not allowed. This
- // is to enforce linear runtime and prevent cases that could result in an infinite loop.
- int offset = ppos.getIndex();
- for (; offset < input.length(); ) {
- int cp = Character.codePointAt(input, offset);
- state.swap();
- for (int i = 0; i < state.prevLength; i++) {
- StateItem item = state.prevItems[i];
- if (DEBUGGING) {
- System.out.println(":" + offset + " " + item);
- }
-
- // In the switch statement below, if you see a line like:
- // if (state.length > 0 && mode == ParseMode.FAST) break;
- // it is used for accelerating the fast parse mode. The check is performed only in the
- // states BEFORE_PREFIX, AFTER_INTEGER_DIGIT, and AFTER_FRACTION_DIGIT, which are the
- // most common states.
-
- switch (item.name) {
- case BEFORE_PREFIX:
- // Beginning of string
- if (mode == ParseMode.LENIENT || mode == ParseMode.FAST) {
- acceptMinusOrPlusSign(cp, StateName.BEFORE_PREFIX, state, item, false);
- if (state.length > 0 && mode == ParseMode.FAST) break;
- }
- acceptIntegerDigit(cp, StateName.AFTER_INTEGER_DIGIT, state, item);
- if (state.length > 0 && mode == ParseMode.FAST) break;
- acceptBidi(cp, StateName.BEFORE_PREFIX, state, item);
- if (state.length > 0 && mode == ParseMode.FAST) break;
- acceptWhitespace(cp, StateName.BEFORE_PREFIX, state, item);
- if (state.length > 0 && mode == ParseMode.FAST) break;
- acceptPadding(cp, StateName.BEFORE_PREFIX, state, item);
- if (state.length > 0 && mode == ParseMode.FAST) break;
- acceptNan(cp, StateName.BEFORE_SUFFIX, state, item);
- if (state.length > 0 && mode == ParseMode.FAST) break;
- acceptInfinity(cp, StateName.BEFORE_SUFFIX, state, item);
- if (state.length > 0 && mode == ParseMode.FAST) break;
- if (!integerOnly) {
- acceptDecimalPoint(cp, StateName.AFTER_FRACTION_DIGIT, state, item);
- if (state.length > 0 && mode == ParseMode.FAST) break;
- }
- if (mode == ParseMode.LENIENT || mode == ParseMode.STRICT) {
- acceptPrefix(cp, StateName.AFTER_PREFIX, state, item);
- }
- if (mode == ParseMode.LENIENT || mode == ParseMode.FAST) {
- acceptGrouping(cp, StateName.AFTER_INTEGER_DIGIT, state, item);
- if (state.length > 0 && mode == ParseMode.FAST) break;
- if (parseCurrency) {
- acceptCurrency(cp, StateName.BEFORE_PREFIX, state, item);
- }
- }
- break;
-
- case AFTER_PREFIX:
- // Prefix is consumed
- acceptBidi(cp, StateName.AFTER_PREFIX, state, item);
- acceptPadding(cp, StateName.AFTER_PREFIX, state, item);
- acceptNan(cp, StateName.BEFORE_SUFFIX, state, item);
- acceptInfinity(cp, StateName.BEFORE_SUFFIX, state, item);
- acceptIntegerDigit(cp, StateName.AFTER_INTEGER_DIGIT, state, item);
- if (!integerOnly) {
- acceptDecimalPoint(cp, StateName.AFTER_FRACTION_DIGIT, state, item);
- }
- if (mode == ParseMode.LENIENT || mode == ParseMode.FAST) {
- acceptWhitespace(cp, StateName.AFTER_PREFIX, state, item);
- acceptGrouping(cp, StateName.AFTER_INTEGER_DIGIT, state, item);
- if (parseCurrency) {
- acceptCurrency(cp, StateName.AFTER_PREFIX, state, item);
- }
- }
- break;
-
- case AFTER_INTEGER_DIGIT:
- // Previous character was an integer digit (or grouping/whitespace)
- acceptIntegerDigit(cp, StateName.AFTER_INTEGER_DIGIT, state, item);
- if (state.length > 0 && mode == ParseMode.FAST) break;
- if (!integerOnly) {
- acceptDecimalPoint(cp, StateName.AFTER_FRACTION_DIGIT, state, item);
- if (state.length > 0 && mode == ParseMode.FAST) break;
- }
- acceptGrouping(cp, StateName.AFTER_INTEGER_DIGIT, state, item);
- if (state.length > 0 && mode == ParseMode.FAST) break;
- acceptBidi(cp, StateName.AFTER_INTEGER_DIGIT, state, item);
- if (state.length > 0 && mode == ParseMode.FAST) break;
- acceptPadding(cp, StateName.BEFORE_SUFFIX, state, item);
- if (state.length > 0 && mode == ParseMode.FAST) break;
- if (!ignoreExponent) {
- acceptExponentSeparator(cp, StateName.AFTER_EXPONENT_SEPARATOR, state, item);
- if (state.length > 0 && mode == ParseMode.FAST) break;
- }
- if (mode == ParseMode.LENIENT || mode == ParseMode.STRICT) {
- acceptSuffix(cp, StateName.AFTER_SUFFIX, state, item);
- }
- if (mode == ParseMode.LENIENT || mode == ParseMode.FAST) {
- acceptWhitespace(cp, StateName.BEFORE_SUFFIX, state, item);
- if (state.length > 0 && mode == ParseMode.FAST) break;
- acceptMinusOrPlusSign(cp, StateName.BEFORE_SUFFIX, state, item, false);
- if (state.length > 0 && mode == ParseMode.FAST) break;
- if (parseCurrency) {
- acceptCurrency(cp, StateName.BEFORE_SUFFIX, state, item);
- }
- }
- break;
-
- case AFTER_FRACTION_DIGIT:
- // We encountered a decimal point
- acceptFractionDigit(cp, StateName.AFTER_FRACTION_DIGIT, state, item);
- if (state.length > 0 && mode == ParseMode.FAST) break;
- acceptBidi(cp, StateName.AFTER_FRACTION_DIGIT, state, item);
- if (state.length > 0 && mode == ParseMode.FAST) break;
- acceptPadding(cp, StateName.BEFORE_SUFFIX, state, item);
- if (state.length > 0 && mode == ParseMode.FAST) break;
- if (!ignoreExponent) {
- acceptExponentSeparator(cp, StateName.AFTER_EXPONENT_SEPARATOR, state, item);
- if (state.length > 0 && mode == ParseMode.FAST) break;
- }
- if (mode == ParseMode.LENIENT || mode == ParseMode.STRICT) {
- acceptSuffix(cp, StateName.AFTER_SUFFIX, state, item);
- }
- if (mode == ParseMode.LENIENT || mode == ParseMode.FAST) {
- acceptWhitespace(cp, StateName.BEFORE_SUFFIX, state, item);
- if (state.length > 0 && mode == ParseMode.FAST) break;
- acceptMinusOrPlusSign(cp, StateName.BEFORE_SUFFIX, state, item, false);
- if (state.length > 0 && mode == ParseMode.FAST) break;
- if (parseCurrency) {
- acceptCurrency(cp, StateName.BEFORE_SUFFIX, state, item);
- }
- }
- break;
-
- case AFTER_EXPONENT_SEPARATOR:
- acceptBidi(cp, StateName.AFTER_EXPONENT_SEPARATOR, state, item);
- acceptMinusOrPlusSign(cp, StateName.AFTER_EXPONENT_SEPARATOR, state, item, true);
- acceptExponentDigit(cp, StateName.AFTER_EXPONENT_DIGIT, state, item);
- break;
-
- case AFTER_EXPONENT_DIGIT:
- acceptBidi(cp, StateName.AFTER_EXPONENT_DIGIT, state, item);
- acceptPadding(cp, StateName.BEFORE_SUFFIX_SEEN_EXPONENT, state, item);
- acceptExponentDigit(cp, StateName.AFTER_EXPONENT_DIGIT, state, item);
- if (mode == ParseMode.LENIENT || mode == ParseMode.STRICT) {
- acceptSuffix(cp, StateName.AFTER_SUFFIX, state, item);
- }
- if (mode == ParseMode.LENIENT || mode == ParseMode.FAST) {
- acceptWhitespace(cp, StateName.BEFORE_SUFFIX_SEEN_EXPONENT, state, item);
- acceptMinusOrPlusSign(cp, StateName.BEFORE_SUFFIX, state, item, false);
- if (parseCurrency) {
- acceptCurrency(cp, StateName.BEFORE_SUFFIX_SEEN_EXPONENT, state, item);
- }
- }
- break;
-
- case BEFORE_SUFFIX:
- // Accept whitespace, suffixes, and exponent separators
- acceptBidi(cp, StateName.BEFORE_SUFFIX, state, item);
- acceptPadding(cp, StateName.BEFORE_SUFFIX, state, item);
- if (!ignoreExponent) {
- acceptExponentSeparator(cp, StateName.AFTER_EXPONENT_SEPARATOR, state, item);
- }
- if (mode == ParseMode.LENIENT || mode == ParseMode.STRICT) {
- acceptSuffix(cp, StateName.AFTER_SUFFIX, state, item);
- }
- if (mode == ParseMode.LENIENT || mode == ParseMode.FAST) {
- acceptWhitespace(cp, StateName.BEFORE_SUFFIX, state, item);
- acceptMinusOrPlusSign(cp, StateName.BEFORE_SUFFIX, state, item, false);
- if (parseCurrency) {
- acceptCurrency(cp, StateName.BEFORE_SUFFIX, state, item);
- }
- }
- break;
-
- case BEFORE_SUFFIX_SEEN_EXPONENT:
- // Accept whitespace and suffixes but not exponent separators
- acceptBidi(cp, StateName.BEFORE_SUFFIX_SEEN_EXPONENT, state, item);
- acceptPadding(cp, StateName.BEFORE_SUFFIX_SEEN_EXPONENT, state, item);
- if (mode == ParseMode.LENIENT || mode == ParseMode.STRICT) {
- acceptSuffix(cp, StateName.AFTER_SUFFIX, state, item);
- }
- if (mode == ParseMode.LENIENT || mode == ParseMode.FAST) {
- acceptWhitespace(cp, StateName.BEFORE_SUFFIX_SEEN_EXPONENT, state, item);
- acceptMinusOrPlusSign(cp, StateName.BEFORE_SUFFIX_SEEN_EXPONENT, state, item, false);
- if (parseCurrency) {
- acceptCurrency(cp, StateName.BEFORE_SUFFIX_SEEN_EXPONENT, state, item);
- }
- }
- break;
-
- case AFTER_SUFFIX:
- if ((mode == ParseMode.LENIENT || mode == ParseMode.FAST) && parseCurrency) {
- // Continue traversing in case there is a currency symbol to consume
- acceptBidi(cp, StateName.AFTER_SUFFIX, state, item);
- acceptPadding(cp, StateName.AFTER_SUFFIX, state, item);
- acceptWhitespace(cp, StateName.AFTER_SUFFIX, state, item);
- acceptMinusOrPlusSign(cp, StateName.AFTER_SUFFIX, state, item, false);
- if (parseCurrency) {
- acceptCurrency(cp, StateName.AFTER_SUFFIX, state, item);
- }
- }
- // Otherwise, do not accept any more characters.
- break;
-
- case INSIDE_CURRENCY:
- acceptCurrencyOffset(cp, state, item);
- break;
-
- case INSIDE_DIGIT:
- acceptDigitTrieOffset(cp, state, item);
- break;
-
- case INSIDE_STRING:
- acceptStringOffset(cp, state, item);
- // Accept arbitrary bidi in the middle of strings.
- if (state.length == 0 && UNISET_BIDI.contains(cp)) {
- state.getNext().copyFrom(item, item.name, cp);
- }
- break;
-
- case INSIDE_AFFIX_PATTERN:
- acceptAffixPatternOffset(cp, state, item);
- // Accept arbitrary bidi and whitespace (if lenient) in the middle of affixes.
- if (state.length == 0 && isIgnorable(cp, state)) {
- state.getNext().copyFrom(item, item.name, cp);
- }
- break;
- }
- }
-
- if (state.length == 0) {
- // No parse paths continue past this point. We have found the longest parsable string
- // from the input. Restore previous state without the offset and break.
- state.swapBack();
- break;
- }
-
- offset += Character.charCount(cp);
- }
-
- // Post-processing
- if (state.length == 0) {
- if (DEBUGGING) {
- System.out.println("No matches found");
- System.out.println("- - - - - - - - - -");
- }
- return null;
- } else {
-
- // Loop through the candidates. "continue" skips a candidate as invalid.
- StateItem best = null;
- outer:
- for (int i = 0; i < state.length; i++) {
- StateItem item = state.items[i];
-
- if (DEBUGGING) {
- System.out.println(":end " + item);
- }
-
- // Check that at least one digit was read.
- if (!item.hasNumber()) {
- if (DEBUGGING) System.out.println("-> rejected due to no number value");
- continue;
- }
-
- if (mode == ParseMode.STRICT) {
- // Perform extra checks for strict mode.
- // We require that the affixes match.
- boolean sawPrefix = item.sawPrefix || (item.affix != null && item.affix.p.isEmpty());
- boolean sawSuffix = item.sawSuffix || (item.affix != null && item.affix.s.isEmpty());
- boolean hasEmptyAffix =
- state.affixHolders.contains(AffixHolder.EMPTY_POSITIVE)
- || state.affixHolders.contains(AffixHolder.EMPTY_NEGATIVE);
- if (sawPrefix && sawSuffix) {
- // OK
- } else if (!sawPrefix && !sawSuffix && hasEmptyAffix) {
- // OK
- } else {
- // Has a prefix or suffix that doesn't match
- if (DEBUGGING) System.out.println("-> rejected due to mismatched prefix/suffix");
- continue;
- }
-
- // Check that grouping sizes are valid.
- int grouping1 = properties.getGroupingSize();
- int grouping2 = properties.getSecondaryGroupingSize();
- grouping1 = grouping1 > 0 ? grouping1 : grouping2;
- grouping2 = grouping2 > 0 ? grouping2 : grouping1;
- long groupingWidths = item.groupingWidths;
- int numGroupingRegions = 16 - Long.numberOfLeadingZeros(groupingWidths) / 4;
- // If the last grouping is zero, accept strings like "1," but reject string like "1,.23"
- // Strip off multiple last-groupings to handle cases like "123,," or "123 "
- while (numGroupingRegions > 1 && (groupingWidths & 0xf) == 0) {
- if (item.sawDecimalPoint) {
- if (DEBUGGING) System.out.println("-> rejected due to decimal point after grouping");
- continue outer;
- } else {
- groupingWidths >>>= 4;
- numGroupingRegions--;
- }
- }
- if (grouping1 < 0) {
- // OK (no grouping data available)
- } else if (numGroupingRegions <= 1) {
- // OK (no grouping digits)
- } else if ((groupingWidths & 0xf) != grouping1) {
- // First grouping size is invalid
- if (DEBUGGING) System.out.println("-> rejected due to first grouping violation");
- continue;
- } else if (((groupingWidths >>> ((numGroupingRegions - 1) * 4)) & 0xf) > grouping2) {
- // String like "1234,567" where the highest grouping is too large
- if (DEBUGGING) System.out.println("-> rejected due to final grouping violation");
- continue;
- } else {
- for (int j = 1; j < numGroupingRegions - 1; j++) {
- if (((groupingWidths >>> (j * 4)) & 0xf) != grouping2) {
- // A grouping size somewhere in the middle is invalid
- if (DEBUGGING) System.out.println("-> rejected due to inner grouping violation");
- continue outer;
- }
- }
- }
- }
-
- // Optionally require that the presence of a decimal point matches the pattern.
- if (properties.getDecimalPatternMatchRequired()
- && item.sawDecimalPoint != PositiveDecimalFormat.allowsDecimalPoint(properties)) {
- if (DEBUGGING) System.out.println("-> rejected due to decimal point violation");
- continue;
- }
-
- // When parsing currencies, require that a currency symbol was found.
- if (parseCurrency && !item.sawCurrency) {
- if (DEBUGGING) System.out.println("-> rejected due to lack of currency");
- continue;
- }
-
- // If we get here, then this candidate is acceptable.
- // Use the earliest candidate in the list, or the one with the highest score.
- if (best == null) {
- best = item;
- } else if (item.score > best.score) {
- best = item;
- }
- }
-
- if (DEBUGGING) {
- System.out.println("- - - - - - - - - -");
- }
-
- if (best != null) {
- ppos.setIndex(offset - best.trailingCount);
- return best;
- } else {
- ppos.setErrorIndex(offset);
- return null;
- }
- }
- }
-
- /**
- * If <code>cp</code> is whitespace (as determined by the unicode set {@link #UNISET_WHITESPACE}),
- * copies <code>item</code> to the new list in <code>state</code> and sets its state name to
- * <code>nextName</code>.
- *
- * @param cp The code point to check.
- * @param nextName The new state name if the check passes.
- * @param state The state object to update.
- * @param item The old state leading into the code point.
- */
- private static void acceptWhitespace(
- int cp, StateName nextName, ParserState state, StateItem item) {
- if (UNISET_WHITESPACE.contains(cp)) {
- state.getNext().copyFrom(item, nextName, cp);
- }
- }
-
- /**
- * If <code>cp</code> is a bidi control character (as determined by the unicode set {@link
- * #UNISET_BIDI}), copies <code>item</code> to the new list in <code>state</code> and sets its
- * state name to <code>nextName</code>.
- *
- * @param cp The code point to check.
- * @param nextName The new state name if the check passes.
- * @param state The state object to update.
- * @param item The old state leading into the code point.
- */
- private static void acceptBidi(int cp, StateName nextName, ParserState state, StateItem item) {
- if (UNISET_BIDI.contains(cp)) {
- state.getNext().copyFrom(item, nextName, cp);
- }
- }
-
- /**
- * If <code>cp</code> is a padding character (as determined by {@link ParserState#paddingCp}),
- * copies <code>item</code> to the new list in <code>state</code> and sets its state name to
- * <code>nextName</code>.
- *
- * @param cp The code point to check.
- * @param nextName The new state name if the check passes.
- * @param state The state object to update.
- * @param item The old state leading into the code point.
- */
- private static void acceptPadding(int cp, StateName nextName, ParserState state, StateItem item) {
- CharSequence padding = state.properties.getPadString();
- if (padding == null || padding.length() == 0) return;
- int referenceCp = Character.codePointAt(padding, 0);
- if (cp == referenceCp) {
- state.getNext().copyFrom(item, nextName, cp);
- }
- }
-
- private static void acceptIntegerDigit(
- int cp, StateName nextName, ParserState state, StateItem item) {
- acceptDigitHelper(cp, nextName, state, item, DigitType.INTEGER);
- }
-
- private static void acceptFractionDigit(
- int cp, StateName nextName, ParserState state, StateItem item) {
- acceptDigitHelper(cp, nextName, state, item, DigitType.FRACTION);
- }
-
- private static void acceptExponentDigit(
- int cp, StateName nextName, ParserState state, StateItem item) {
- acceptDigitHelper(cp, nextName, state, item, DigitType.EXPONENT);
- }
-
- /**
- * If <code>cp</code> is a digit character (as determined by either {@link UCharacter#digit} or
- * {@link ParserState#digitCps}), copies <code>item</code> to the new list in <code>state</code>
- * and sets its state name to one determined by <code>type</code>. Also copies the digit into a
- * field in the new item determined by <code>type</code>.
- *
- * <p>This function guarantees that it will add no more than one {@link StateItem} to the {@link
- * ParserState}. This means that {@link ParserState#lastInsertedIndex()} can be called to access
- * the {@link StateItem} that was inserted.
- *
- * @param cp The code point to check.
- * @param nextName The state to set if a digit is accepted.
- * @param state The state object to update.
- * @param item The old state leading into the code point.
- * @param type The digit type, which determines the next state and the field into which to insert
- * the digit.
- */
- private static void acceptDigitHelper(
- int cp, StateName nextName, ParserState state, StateItem item, DigitType type) {
- // Check the Unicode digit character property
- byte digit = (byte) UCharacter.digit(cp, 10);
- StateItem next = null;
-
- // Look for the digit:
- if (digit >= 0) {
- // Code point is a number
- next = state.getNext().copyFrom(item, nextName, -1);
- }
-
- // Do not perform the expensive string manipulations in fast mode.
- if (digit < 0 && (state.mode == ParseMode.LENIENT || state.mode == ParseMode.STRICT)) {
- if (state.digitTrie == null) {
- // Check custom digits, all of which are at most one code point
- for (byte d = 0; d < 10; d++) {
- int referenceCp = Character.codePointAt(state.symbols.getDigitStringsLocal()[d], 0);
- if (cp == referenceCp) {
- digit = d;
- next = state.getNext().copyFrom(item, nextName, -1);
- }
- }
- } else {
- // Custom digits have more than one code point
- acceptDigitTrie(cp, nextName, state, item, type);
- }
- }
-
- // Save state:
- if (next != null) {
- next.appendDigit(digit, type);
- if (type == DigitType.INTEGER && (next.groupingWidths & 0xf) < 15) {
- next.groupingWidths++;
- }
- }
- }
-
- /**
- * If <code>cp</code> is a sign (as determined by the unicode sets {@link #UNISET_PLUS} and {@link
- * #UNISET_MINUS}), copies <code>item</code> to the new list in <code>state</code>. Loops back to
- * the same state name.
- *
- * @param cp The code point to check.
- * @param state The state object to update.
- * @param item The old state leading into the code point.
- */
- private static void acceptMinusOrPlusSign(
- int cp, StateName nextName, ParserState state, StateItem item, boolean exponent) {
- acceptMinusOrPlusSign(cp, nextName, null, state, item, exponent);
- }
-
- private static void acceptMinusOrPlusSign(
- int cp,
- StateName returnTo1,
- StateName returnTo2,
- ParserState state,
- StateItem item,
- boolean exponent) {
- if (UNISET_PLUS.contains(cp)) {
- StateItem next = state.getNext().copyFrom(item, returnTo1, -1);
- next.returnTo1 = returnTo2;
- } else if (UNISET_MINUS.contains(cp)) {
- StateItem next = state.getNext().copyFrom(item, returnTo1, -1);
- next.returnTo1 = returnTo2;
- if (exponent) {
- next.sawNegativeExponent = true;
- } else {
- next.sawNegative = true;
- }
- }
- }
-
- /**
- * If <code>cp</code> is a grouping separator (as determined by the unicode set {@link
- * #UNISET_GROUPING}), copies <code>item</code> to the new list in <code>state</code> and loops
- * back to the same state. Also accepts if <code>cp</code> is the locale-specific grouping
- * separator in {@link ParserState#groupingCp}, in which case the {@link
- * StateItem#usesLocaleSymbols} flag is also set.
- *
- * @param cp The code point to check.
- * @param state The state object to update.
- * @param item The old state leading into the code point.
- */
- private static void acceptGrouping(
- int cp, StateName nextName, ParserState state, StateItem item) {
- // Do not accept mixed grouping separators in the same string.
- if (item.groupingCp == -1) {
- // First time seeing a grouping separator.
- SeparatorType cpType = SeparatorType.fromCp(cp, state.mode);
-
- // Always accept if exactly the same as the locale symbol.
- // Otherwise, reject if UNKNOWN or in the same class as the decimal separator.
- if (cp != state.groupingCp1 && cp != state.groupingCp2) {
- if (cpType == SeparatorType.UNKNOWN) {
- return;
- }
- if (cpType == SeparatorType.COMMA_LIKE
- && (state.decimalType1 == SeparatorType.COMMA_LIKE
- || state.decimalType2 == SeparatorType.COMMA_LIKE)) {
- return;
- }
- if (cpType == SeparatorType.PERIOD_LIKE
- && (state.decimalType1 == SeparatorType.PERIOD_LIKE
- || state.decimalType2 == SeparatorType.PERIOD_LIKE)) {
- return;
- }
- }
-
- // A match was found.
- StateItem next = state.getNext().copyFrom(item, nextName, cp);
- next.groupingCp = cp;
- next.groupingWidths <<= 4;
- } else {
- // Have already seen a grouping separator.
- if (cp == item.groupingCp) {
- StateItem next = state.getNext().copyFrom(item, nextName, cp);
- next.groupingWidths <<= 4;
- }
- }
- }
-
- /**
- * If <code>cp</code> is a decimal (as determined by the unicode set {@link #UNISET_DECIMAL}),
- * copies <code>item</code> to the new list in <code>state</code> and goes to {@link
- * StateName#AFTER_FRACTION_DIGIT}. Also accepts if <code>cp</code> is the locale-specific decimal
- * point in {@link ParserState#decimalCp}, in which case the {@link StateItem#usesLocaleSymbols}
- * flag is also set.
- *
- * @param cp The code point to check.
- * @param state The state object to update.
- * @param item The old state leading into the code point.
- */
- private static void acceptDecimalPoint(
- int cp, StateName nextName, ParserState state, StateItem item) {
- if (cp == item.groupingCp) {
- // Don't accept a decimal point that is the same as the grouping separator
- return;
- }
-
- SeparatorType cpType = SeparatorType.fromCp(cp, state.mode);
-
- // We require that the decimal separator be in the same class as the locale.
- if (cpType != state.decimalType1 && cpType != state.decimalType2) {
- return;
- }
-
- // If in UNKNOWN or OTHER, require an exact match.
- if (cpType == SeparatorType.OTHER_GROUPING || cpType == SeparatorType.UNKNOWN) {
- if (cp != state.decimalCp1 && cp != state.decimalCp2) {
- return;
- }
- }
-
- // A match was found.
- StateItem next = state.getNext().copyFrom(item, nextName, -1);
- next.sawDecimalPoint = true;
- }
-
- private static void acceptNan(int cp, StateName nextName, ParserState state, StateItem item) {
- CharSequence nan = state.symbols.getNaN();
- long added = acceptString(cp, nextName, null, state, item, nan, 0);
-
- // Set state in the items that were added by the function call
- for (int i = Long.numberOfTrailingZeros(added); (1L << i) <= added; i++) {
- if (((1L << i) & added) != 0) {
- state.getItem(i).sawNaN = true;
- }
- }
- }
-
- private static void acceptInfinity(
- int cp, StateName nextName, ParserState state, StateItem item) {
- CharSequence inf = state.symbols.getInfinity();
- long added = acceptString(cp, nextName, null, state, item, inf, 0);
-
- // Set state in the items that were added by the function call
- for (int i = Long.numberOfTrailingZeros(added); (1L << i) <= added; i++) {
- if (((1L << i) & added) != 0) {
- state.getItem(i).sawInfinity = true;
- }
- }
- }
-
- private static void acceptExponentSeparator(
- int cp, StateName nextName, ParserState state, StateItem item) {
- CharSequence exp = state.symbols.getExponentSeparator();
- acceptString(cp, nextName, null, state, item, exp, 0);
- }
-
- private static void acceptPrefix(int cp, StateName nextName, ParserState state, StateItem item) {
- for (AffixHolder holder : state.affixHolders) {
- acceptAffixHolder(cp, nextName, state, item, holder, true);
- }
- }
-
- private static void acceptSuffix(int cp, StateName nextName, ParserState state, StateItem item) {
- if (item.affix != null) {
- acceptAffixHolder(cp, nextName, state, item, item.affix, false);
- } else {
- for (AffixHolder holder : state.affixHolders) {
- acceptAffixHolder(cp, nextName, state, item, holder, false);
- }
- }
- }
-
- private static void acceptAffixHolder(
- int cp,
- StateName nextName,
- ParserState state,
- StateItem item,
- AffixHolder holder,
- boolean prefix) {
- if (holder == null) return;
- String str = prefix ? holder.p : holder.s;
- if (holder.strings) {
- long added = acceptString(cp, nextName, null, state, item, str, 0);
- // At most one item can be added upon consuming a string.
- if (added != 0) {
- int i = state.lastInsertedIndex();
- // The following six lines are duplicated below; not enough for their own function.
- state.getItem(i).affix = holder;
- if (prefix) state.getItem(i).sawPrefix = true;
- if (!prefix) state.getItem(i).sawSuffix = true;
- if (holder.negative) state.getItem(i).sawNegative = true;
- state.getItem(i).score++; // reward for consuming a prefix/suffix.
- }
- } else {
- long added = acceptAffixPattern(cp, nextName, state, item, str, 0);
- // Multiple items can be added upon consuming an affix pattern.
- for (int i = Long.numberOfTrailingZeros(added); (1L << i) <= added; i++) {
- if (((1L << i) & added) != 0) {
- // The following six lines are duplicated above; not enough for their own function.
- state.getItem(i).affix = holder;
- if (prefix) state.getItem(i).sawPrefix = true;
- if (!prefix) state.getItem(i).sawSuffix = true;
- if (holder.negative) state.getItem(i).sawNegative = true;
- state.getItem(i).score++; // reward for consuming a prefix/suffix.
- }
- }
- }
- }
-
- private static void acceptStringOffset(int cp, ParserState state, StateItem item) {
- acceptString(
- cp, item.returnTo1, item.returnTo2, state, item, item.currentString, item.currentOffset);
- }
-
- /**
- * Accepts a code point if the code point is compatible with the string at the given offset.
- *
- * <p>This method will add no more than one {@link StateItem} to the {@link ParserState}, which
- * means that at most one bit will be set in the return value, corresponding to the return value
- * of {@link ParserState#lastInsertedIndex()}.
- *
- * @param cp The current code point, which will be checked for a match to the string.
- * @param returnTo1 The state to return to after reaching the end of the string.
- * @param returnTo2 The state to save in <code>returnTo1</code> after reaching the end of the
- * string. Set to null if returning to the main state loop.
- * @param state The current {@link ParserState}
- * @param item The current {@link StateItem}
- * @param str The string against which to check for a match.
- * @param offset The number of chars into the string. Initial value should be 0.
- * @return A bitmask where the bits correspond to the items that were added. Set to 0L if no items
- * were added.
- */
- private static long acceptString(
- int cp,
- StateName returnTo1,
- StateName returnTo2,
- ParserState state,
- StateItem item,
- CharSequence str,
- int offset) {
- if (str == null || str.length() == 0) return 0L;
-
- // Fast path for fast mode
- if (state.mode == ParseMode.FAST && Character.codePointAt(str, offset) != cp) return 0L;
-
- // Skip over bidi code points at the beginning of the string.
- // They will be accepted in the main loop.
- int count = 0;
- int referenceCp = -1;
- boolean equals = false;
- for (; offset < str.length(); offset += count) {
- referenceCp = Character.codePointAt(str, offset);
- count = Character.charCount(referenceCp);
- equals = codePointEquals(cp, referenceCp, state);
- if (!UNISET_BIDI.contains(cp)) break;
- }
-
- if (equals) {
- // Matches first code point of the string
- StateItem next = state.getNext().copyFrom(item, null, cp);
-
- // Skip over ignorable code points in the middle of the string.
- // They will be accepted in the main loop.
- offset += count;
- for (; offset < str.length(); offset += count) {
- referenceCp = Character.codePointAt(str, offset);
- count = Character.charCount(referenceCp);
- if (!UNISET_BIDI.contains(cp)) break;
- }
-
- if (offset < str.length()) {
- // String has more interesting code points.
- next.name = StateName.INSIDE_STRING;
- next.returnTo1 = returnTo1;
- next.returnTo2 = returnTo2;
- next.currentString = str;
- next.currentOffset = offset;
- } else {
- // We've reached the end of the string.
- next.name = returnTo1;
- next.trailingCount = 0;
- next.returnTo1 = returnTo2;
- next.returnTo2 = null;
- }
- return 1L << state.lastInsertedIndex();
- }
- return 0L;
- }
-
- private static void acceptAffixPatternOffset(int cp, ParserState state, StateItem item) {
- acceptAffixPattern(
- cp, item.returnTo1, state, item, item.currentAffixPattern, item.currentStepwiseParserTag);
- }
-
- /**
- * Accepts a code point if the code point is compatible with the affix pattern at the offset
- * encoded in the tag argument.
- *
- * @param cp The current code point, which will be checked for a match to the string.
- * @param returnTo The state to return to after reaching the end of the string.
- * @param state The current {@link ParserState}
- * @param item The current {@link StateItem}
- * @param str The string containing the affix pattern.
- * @param tag The current state of the stepwise parser. Initial value should be 0L.
- * @return A bitmask where the bits correspond to the items that were added. Set to 0L if no items
- * were added.
- */
- private static long acceptAffixPattern(
- int cp, StateName returnTo, ParserState state, StateItem item, CharSequence str, long tag) {
- if (str == null || str.length() == 0) return 0L;
-
- // Skip over ignorable code points at the beginning of the affix pattern.
- // They will be accepted in the main loop.
- int typeOrCp = 0;
- boolean hasNext = true;
- while (hasNext) {
- tag = AffixPatternUtils.nextToken(tag, str);
- typeOrCp = AffixPatternUtils.getTypeOrCp(tag);
- hasNext = AffixPatternUtils.hasNext(tag, str);
- if (typeOrCp < 0 || !isIgnorable(typeOrCp, state)) break;
- }
-
- // Convert from the returned tag to a code point, string, or currency to check
- int resolvedCp = -1;
- CharSequence resolvedStr = null;
- boolean resolvedMinusSign = false;
- boolean resolvedPlusSign = false;
- boolean resolvedCurrency = false;
- if (typeOrCp < 0) {
- // Symbol
- switch (typeOrCp) {
- case AffixPatternUtils.TYPE_MINUS_SIGN:
- resolvedMinusSign = true;
- break;
- case AffixPatternUtils.TYPE_PLUS_SIGN:
- resolvedPlusSign = true;
- break;
- case AffixPatternUtils.TYPE_PERCENT:
- resolvedStr = state.symbols.getPercentString();
- break;
- case AffixPatternUtils.TYPE_PERMILLE:
- resolvedStr = state.symbols.getPerMillString();
- break;
- case AffixPatternUtils.TYPE_CURRENCY_SINGLE:
- case AffixPatternUtils.TYPE_CURRENCY_DOUBLE:
- case AffixPatternUtils.TYPE_CURRENCY_TRIPLE:
- resolvedCurrency = true;
- break;
- default:
- throw new AssertionError();
- }
- } else {
- resolvedCp = typeOrCp;
- }
-
- // Skip over ignorable code points in the middle of the affix pattern.
- // They will be accepted in the main loop.
- while (hasNext) {
- long futureTag = AffixPatternUtils.nextToken(tag, str);
- int futureTypeOrCp = AffixPatternUtils.getTypeOrCp(futureTag);
- if (futureTypeOrCp < 0 || !isIgnorable(futureTypeOrCp, state)) break;
- tag = futureTag;
- typeOrCp = futureTypeOrCp;
- hasNext = AffixPatternUtils.hasNext(tag, str);
- }
-
- long added = 0L;
- if (resolvedCp >= 0) {
- // Code point
- if (!codePointEquals(cp, resolvedCp, state)) return 0L;
- StateItem next = state.getNext().copyFrom(item, null, cp);
-
- if (hasNext) {
- // Additional tokens in affix string.
- next.name = StateName.INSIDE_AFFIX_PATTERN;
- next.returnTo1 = returnTo;
- } else {
- // Reached last token in affix string.
- next.name = returnTo;
- next.trailingCount = 0;
- next.returnTo1 = null;
- }
- added |= 1L << state.lastInsertedIndex();
- }
- if (resolvedMinusSign || resolvedPlusSign) {
- // Sign
- if (hasNext) {
- acceptMinusOrPlusSign(cp, StateName.INSIDE_AFFIX_PATTERN, returnTo, state, item, false);
- } else {
- acceptMinusOrPlusSign(cp, returnTo, null, state, item, false);
- }
- // Decide whether to accept a custom string
- if (resolvedMinusSign) {
- String mss = state.symbols.getMinusSignString();
- int mssCp = Character.codePointAt(mss, 0);
- if (mss.length() != Character.charCount(mssCp) || !UNISET_MINUS.contains(mssCp)) {
- resolvedStr = mss;
- }
- }
- if (resolvedPlusSign) {
- String pss = state.symbols.getPlusSignString();
- int pssCp = Character.codePointAt(pss, 0);
- if (pss.length() != Character.charCount(pssCp) || !UNISET_MINUS.contains(pssCp)) {
- resolvedStr = pss;
- }
- }
- }
- if (resolvedStr != null) {
- // String symbol
- if (hasNext) {
- added |=
- acceptString(cp, StateName.INSIDE_AFFIX_PATTERN, returnTo, state, item, resolvedStr, 0);
- } else {
- added |= acceptString(cp, returnTo, null, state, item, resolvedStr, 0);
- }
- }
- if (resolvedCurrency) {
- // Currency symbol
- if (hasNext) {
- added |= acceptCurrency(cp, StateName.INSIDE_AFFIX_PATTERN, returnTo, state, item);
- } else {
- added |= acceptCurrency(cp, returnTo, null, state, item);
- }
- }
-
- // Set state in the items that were added by the function calls
- for (int i = Long.numberOfTrailingZeros(added); (1L << i) <= added; i++) {
- if (((1L << i) & added) != 0) {
- state.getItem(i).currentAffixPattern = str;
- state.getItem(i).currentStepwiseParserTag = tag;
- }
- }
- return added;
- }
-
- /**
- * This method can add up to four items to the new list in <code>state</code>.
- *
- * <p>If <code>cp</code> is equal to any known ISO code or long name, copies <code>item</code> to
- * the new list in <code>state</code> and sets its ISO code to the corresponding currency.
- *
- * <p>If <code>cp</code> is the first code point of any ISO code or long name having more them one
- * code point in length, copies <code>item</code> to the new list in <code>state</code> along with
- * an instance of {@link TextTrieMap.ParseState} for tracking the following code points.
- *
- * @param cp The code point to check.
- * @param state The state object to update.
- * @param item The old state leading into the code point.
- */
- private static void acceptCurrency(
- int cp, StateName nextName, ParserState state, StateItem item) {
- acceptCurrency(cp, nextName, null, state, item);
- }
-
- private static long acceptCurrency(
- int cp, StateName returnTo1, StateName returnTo2, ParserState state, StateItem item) {
- if (item.sawCurrency) return 0L;
- long added = 0L;
-
- // Accept from local currency information
- String str1, str2;
- Currency currency = state.properties.getCurrency();
- if (currency != null) {
- str1 = currency.getName(state.symbols.getULocale(), Currency.SYMBOL_NAME, null);
- str2 = currency.getCurrencyCode();
- // TODO: Should we also accept long names? In currency mode, they are in the CLDR data.
- } else {
- currency = state.symbols.getCurrency();
- str1 = state.symbols.getCurrencySymbol();
- str2 = state.symbols.getInternationalCurrencySymbol();
- }
- added |= acceptString(cp, returnTo1, returnTo2, state, item, str1, 0);
- added |= acceptString(cp, returnTo1, returnTo2, state, item, str2, 0);
- for (int i = Long.numberOfTrailingZeros(added); (1L << i) <= added; i++) {
- if (((1L << i) & added) != 0) {
- state.getItem(i).sawCurrency = true;
- state.getItem(i).isoCode = str2;
- }
- }
-
- // Accept from CLDR data
- if (state.parseCurrency) {
- ULocale uloc = state.symbols.getULocale();
- TextTrieMap<Currency.CurrencyStringInfo>.ParseState trie1 =
- Currency.openParseState(uloc, cp, Currency.LONG_NAME);
- TextTrieMap<Currency.CurrencyStringInfo>.ParseState trie2 =
- Currency.openParseState(uloc, cp, Currency.SYMBOL_NAME);
- added |= acceptCurrencyHelper(cp, returnTo1, returnTo2, state, item, trie1);
- added |= acceptCurrencyHelper(cp, returnTo1, returnTo2, state, item, trie2);
- }
-
- return added;
- }
-
- /**
- * If <code>cp</code> is the next code point of any currency, copies <code>item</code> to the new
- * list in <code>state</code> along with an instance of {@link TextTrieMap.ParseState} for
- * tracking the following code points.
- *
- * <p>This method should only be called in a state following {@link #acceptCurrency}.
- *
- * @param cp The code point to check.
- * @param state The state object to update.
- * @param item The old state leading into the code point.
- */
- private static void acceptCurrencyOffset(int cp, ParserState state, StateItem item) {
- acceptCurrencyHelper(
- cp, item.returnTo1, item.returnTo2, state, item, item.currentCurrencyTrieState);
- }
-
- private static long acceptCurrencyHelper(
- int cp,
- StateName returnTo1,
- StateName returnTo2,
- ParserState state,
- StateItem item,
- TextTrieMap<Currency.CurrencyStringInfo>.ParseState trieState) {
- if (trieState == null) return 0L;
- trieState.accept(cp);
- long added = 0L;
- Iterator<Currency.CurrencyStringInfo> currentMatches = trieState.getCurrentMatches();
- if (currentMatches != null) {
- // Match on current code point
- // TODO: What should happen with multiple currency matches?
- StateItem next = state.getNext().copyFrom(item, returnTo1, -1);
- next.returnTo1 = returnTo2;
- next.returnTo2 = null;
- next.sawCurrency = true;
- next.isoCode = currentMatches.next().getISOCode();
- added |= 1L << state.lastInsertedIndex();
- }
- if (!trieState.atEnd()) {
- // Prepare for matches on future code points
- StateItem next = state.getNext().copyFrom(item, StateName.INSIDE_CURRENCY, -1);
- next.returnTo1 = returnTo1;
- next.returnTo2 = returnTo2;
- next.currentCurrencyTrieState = trieState;
- added |= 1L << state.lastInsertedIndex();
- }
- return added;
- }
-
- private static long acceptDigitTrie(
- int cp, StateName nextName, ParserState state, StateItem item, DigitType type) {
- assert state.digitTrie != null;
- TextTrieMap<Byte>.ParseState trieState = state.digitTrie.openParseState(cp);
- if (trieState == null) return 0L;
- return acceptDigitTrieHelper(cp, nextName, state, item, type, trieState);
- }
-
- private static void acceptDigitTrieOffset(int cp, ParserState state, StateItem item) {
- acceptDigitTrieHelper(
- cp, item.returnTo1, state, item, item.currentDigitType, item.currentDigitTrieState);
- }
-
- private static long acceptDigitTrieHelper(
- int cp,
- StateName returnTo1,
- ParserState state,
- StateItem item,
- DigitType type,
- TextTrieMap<Byte>.ParseState trieState) {
- if (trieState == null) return 0L;
- trieState.accept(cp);
- long added = 0L;
- Iterator<Byte> currentMatches = trieState.getCurrentMatches();
- if (currentMatches != null) {
- // Match on current code point
- byte digit = currentMatches.next();
- StateItem next = state.getNext().copyFrom(item, returnTo1, -1);
- next.returnTo1 = null;
- next.appendDigit(digit, type);
- added |= 1L << state.lastInsertedIndex();
- }
- if (!trieState.atEnd()) {
- // Prepare for matches on future code points
- StateItem next = state.getNext().copyFrom(item, StateName.INSIDE_DIGIT, -1);
- next.returnTo1 = returnTo1;
- next.currentDigitTrieState = trieState;
- next.currentDigitType = type;
- added |= 1L << state.lastInsertedIndex();
- }
- return added;
- }
-
- /**
- * Checks whether the two given code points are equal after applying case mapping as requested in
- * the ParserState.
- *
- * @see #acceptString
- * @see #acceptAffixPattern
- */
- private static boolean codePointEquals(int cp1, int cp2, ParserState state) {
- if (!state.caseSensitive) {
- cp1 = UCharacter.foldCase(cp1, true);
- cp2 = UCharacter.foldCase(cp2, true);
- }
- return cp1 == cp2;
- }
-
- /**
- * Checks whether the given code point is "ignorable" and should be skipped. BiDi characters are
- * always ignorable, and whitespace is ignorable in lenient mode.
- *
- * @param cp The code point to test. Returns false if cp is negative.
- * @param state The current {@link ParserState}, used for determining strict mode.
- * @return true if cp is bidi or whitespace in lenient mode; false otherwise.
- */
- private static boolean isIgnorable(int cp, ParserState state) {
- if (cp < 0) return false;
- if (UNISET_BIDI.contains(cp)) return true;
- return state.mode == ParseMode.LENIENT && UNISET_WHITESPACE.contains(cp);
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number;
-
-import java.math.BigDecimal;
-
-import com.ibm.icu.impl.number.formatters.PaddingFormat;
-import com.ibm.icu.impl.number.formatters.PaddingFormat.PadPosition;
-import com.ibm.icu.text.DecimalFormatSymbols;
-
-/**
- * Handles parsing and creation of the compact pattern string representation of a decimal format.
- */
-public class PatternString {
-
- /**
- * Parses a pattern string into a new property bag.
- *
- * @param pattern The pattern string, like "#,##0.00"
- * @param ignoreRounding Whether to leave out rounding information (minFrac, maxFrac, and rounding
- * increment) when parsing the pattern. This may be desirable if a custom rounding mode, such
- * as CurrencyUsage, is to be used instead.
- * @return A property bag object.
- * @throws IllegalArgumentException If there is a syntax error in the pattern string.
- */
- public static Properties parseToProperties(String pattern, boolean ignoreRounding) {
- Properties properties = new Properties();
- LdmlDecimalPatternParser.parse(pattern, properties, ignoreRounding);
- return properties;
- }
-
- public static Properties parseToProperties(String pattern) {
- return parseToProperties(pattern, false);
- }
-
- /**
- * Parses a pattern string into an existing property bag. All properties that can be encoded into
- * a pattern string will be overwritten with either their default value or with the value coming
- * from the pattern string. Properties that cannot be encoded into a pattern string, such as
- * rounding mode, are not modified.
- *
- * @param pattern The pattern string, like "#,##0.00"
- * @param properties The property bag object to overwrite.
- * @param ignoreRounding Whether to leave out rounding information (minFrac, maxFrac, and rounding
- * increment) when parsing the pattern. This may be desirable if a custom rounding mode, such
- * as CurrencyUsage, is to be used instead.
- * @throws IllegalArgumentException If there was a syntax error in the pattern string.
- */
- public static void parseToExistingProperties(
- String pattern, Properties properties, boolean ignoreRounding) {
- LdmlDecimalPatternParser.parse(pattern, properties, ignoreRounding);
- }
-
- public static void parseToExistingProperties(String pattern, Properties properties) {
- parseToExistingProperties(pattern, properties, false);
- }
-
- /**
- * Creates a pattern string from a property bag.
- *
- * <p>Since pattern strings support only a subset of the functionality available in a property
- * bag, a new property bag created from the string returned by this function may not be the same
- * as the original property bag.
- *
- * @param properties The property bag to serialize.
- * @return A pattern string approximately serializing the property bag.
- */
- public static String propertiesToString(Properties properties) {
- StringBuilder sb = new StringBuilder();
-
- // Convenience references
- // The Math.min() calls prevent DoS
- int dosMax = 100;
- int groupingSize = Math.min(properties.getSecondaryGroupingSize(), dosMax);
- int firstGroupingSize = Math.min(properties.getGroupingSize(), dosMax);
- int paddingWidth = Math.min(properties.getFormatWidth(), dosMax);
- PadPosition paddingLocation = properties.getPadPosition();
- String paddingString = properties.getPadString();
- int minInt = Math.max(Math.min(properties.getMinimumIntegerDigits(), dosMax), 0);
- int maxInt = Math.min(properties.getMaximumIntegerDigits(), dosMax);
- int minFrac = Math.max(Math.min(properties.getMinimumFractionDigits(), dosMax), 0);
- int maxFrac = Math.min(properties.getMaximumFractionDigits(), dosMax);
- int minSig = Math.min(properties.getMinimumSignificantDigits(), dosMax);
- int maxSig = Math.min(properties.getMaximumSignificantDigits(), dosMax);
- boolean alwaysShowDecimal = properties.getDecimalSeparatorAlwaysShown();
- int exponentDigits = Math.min(properties.getMinimumExponentDigits(), dosMax);
- boolean exponentShowPlusSign = properties.getExponentSignAlwaysShown();
- String pp = properties.getPositivePrefix();
- String ppp = properties.getPositivePrefixPattern();
- String ps = properties.getPositiveSuffix();
- String psp = properties.getPositiveSuffixPattern();
- String np = properties.getNegativePrefix();
- String npp = properties.getNegativePrefixPattern();
- String ns = properties.getNegativeSuffix();
- String nsp = properties.getNegativeSuffixPattern();
-
- // Prefixes
- if (ppp != null) sb.append(ppp);
- AffixPatternUtils.escape(pp, sb);
- int afterPrefixPos = sb.length();
-
- // Figure out the grouping sizes.
- int grouping1, grouping2, grouping;
- if (groupingSize != Math.min(dosMax, Properties.DEFAULT_SECONDARY_GROUPING_SIZE)
- && firstGroupingSize != Math.min(dosMax, Properties.DEFAULT_GROUPING_SIZE)
- && groupingSize != firstGroupingSize) {
- grouping = groupingSize;
- grouping1 = groupingSize;
- grouping2 = firstGroupingSize;
- } else if (groupingSize != Math.min(dosMax, Properties.DEFAULT_SECONDARY_GROUPING_SIZE)) {
- grouping = groupingSize;
- grouping1 = 0;
- grouping2 = groupingSize;
- } else if (firstGroupingSize != Math.min(dosMax, Properties.DEFAULT_GROUPING_SIZE)) {
- grouping = groupingSize;
- grouping1 = 0;
- grouping2 = firstGroupingSize;
- } else {
- grouping = 0;
- grouping1 = 0;
- grouping2 = 0;
- }
- int groupingLength = grouping1 + grouping2 + 1;
-
- // Figure out the digits we need to put in the pattern.
- BigDecimal roundingInterval = properties.getRoundingIncrement();
- StringBuilder digitsString = new StringBuilder();
- int digitsStringScale = 0;
- if (maxSig != Math.min(dosMax, Properties.DEFAULT_MAXIMUM_SIGNIFICANT_DIGITS)) {
- // Significant Digits.
- while (digitsString.length() < minSig) {
- digitsString.append('@');
- }
- while (digitsString.length() < maxSig) {
- digitsString.append('#');
- }
- } else if (roundingInterval != Properties.DEFAULT_ROUNDING_INCREMENT) {
- // Rounding Interval.
- digitsStringScale = -roundingInterval.scale();
- // TODO: Check for DoS here?
- String str = roundingInterval.scaleByPowerOfTen(roundingInterval.scale()).toPlainString();
- if (str.charAt(0) == '\'') {
- // TODO: Unsupported operation exception or fail silently?
- digitsString.append(str, 1, str.length());
- } else {
- digitsString.append(str);
- }
- }
- while (digitsString.length() + digitsStringScale < minInt) {
- digitsString.insert(0, '0');
- }
- while (-digitsStringScale < minFrac) {
- digitsString.append('0');
- digitsStringScale--;
- }
-
- // Write the digits to the string builder
- int m0 = Math.max(groupingLength, digitsString.length() + digitsStringScale);
- m0 = (maxInt != dosMax) ? Math.max(maxInt, m0) - 1 : m0 - 1;
- int mN = (maxFrac != dosMax) ? Math.min(-maxFrac, digitsStringScale) : digitsStringScale;
- for (int magnitude = m0; magnitude >= mN; magnitude--) {
- int di = digitsString.length() + digitsStringScale - magnitude - 1;
- if (di < 0 || di >= digitsString.length()) {
- sb.append('#');
- } else {
- sb.append(digitsString.charAt(di));
- }
- if (magnitude > grouping2 && grouping > 0 && (magnitude - grouping2) % grouping == 0) {
- sb.append(',');
- } else if (magnitude > 0 && magnitude == grouping2) {
- sb.append(',');
- } else if (magnitude == 0 && (alwaysShowDecimal || mN < 0)) {
- sb.append('.');
- }
- }
-
- // Exponential notation
- if (exponentDigits != Math.min(dosMax, Properties.DEFAULT_MINIMUM_EXPONENT_DIGITS)) {
- sb.append('E');
- if (exponentShowPlusSign) {
- sb.append('+');
- }
- for (int i = 0; i < exponentDigits; i++) {
- sb.append('0');
- }
- }
-
- // Suffixes
- int beforeSuffixPos = sb.length();
- if (psp != null) sb.append(psp);
- AffixPatternUtils.escape(ps, sb);
-
- // Resolve Padding
- if (paddingWidth != Properties.DEFAULT_FORMAT_WIDTH) {
- while (paddingWidth - sb.length() > 0) {
- sb.insert(afterPrefixPos, '#');
- beforeSuffixPos++;
- }
- int addedLength;
- switch (paddingLocation) {
- case BEFORE_PREFIX:
- addedLength = escapePaddingString(paddingString, sb, 0);
- sb.insert(0, '*');
- afterPrefixPos += addedLength + 1;
- beforeSuffixPos += addedLength + 1;
- break;
- case AFTER_PREFIX:
- addedLength = escapePaddingString(paddingString, sb, afterPrefixPos);
- sb.insert(afterPrefixPos, '*');
- afterPrefixPos += addedLength + 1;
- beforeSuffixPos += addedLength + 1;
- break;
- case BEFORE_SUFFIX:
- escapePaddingString(paddingString, sb, beforeSuffixPos);
- sb.insert(beforeSuffixPos, '*');
- break;
- case AFTER_SUFFIX:
- sb.append('*');
- escapePaddingString(paddingString, sb, sb.length());
- break;
- }
- }
-
- // Negative affixes
- // Ignore if the negative prefix pattern is "-" and the negative suffix is empty
- if (np != null
- || ns != null
- || (npp == null && nsp != null)
- || (npp != null && (npp.length() != 1 || npp.charAt(0) != '-' || nsp.length() != 0))) {
- sb.append(';');
- if (npp != null) sb.append(npp);
- AffixPatternUtils.escape(np, sb);
- // Copy the positive digit format into the negative.
- // This is optional; the pattern is the same as if '#' were appended here instead.
- sb.append(sb, afterPrefixPos, beforeSuffixPos);
- if (nsp != null) sb.append(nsp);
- AffixPatternUtils.escape(ns, sb);
- }
-
- return sb.toString();
- }
-
- /** @return The number of chars inserted. */
- private static int escapePaddingString(CharSequence input, StringBuilder output, int startIndex) {
- if (input == null || input.length() == 0) input = PaddingFormat.FALLBACK_PADDING_STRING;
- int startLength = output.length();
- if (input.length() == 1) {
- if (input.equals("'")) {
- output.insert(startIndex, "''");
- } else {
- output.insert(startIndex, input);
- }
- } else {
- output.insert(startIndex, '\'');
- int offset = 1;
- for (int i = 0; i < input.length(); i++) {
- // it's okay to deal in chars here because the quote mark is the only interesting thing.
- char ch = input.charAt(i);
- if (ch == '\'') {
- output.insert(startIndex + offset, "''");
- offset += 2;
- } else {
- output.insert(startIndex + offset, ch);
- offset += 1;
- }
- }
- output.insert(startIndex + offset, '\'');
- }
- return output.length() - startLength;
- }
-
- /**
- * Converts a pattern between standard notation and localized notation. Localized notation means
- * that instead of using generic placeholders in the pattern, you use the corresponding
- * locale-specific characters instead. For example, in locale <em>fr-FR</em>, the period in the
- * pattern "0.000" means "decimal" in standard notation (as it does in every other locale), but it
- * means "grouping" in localized notation.
- *
- * @param input The pattern to convert.
- * @param symbols The symbols corresponding to the localized pattern.
- * @param toLocalized true to convert from standard to localized notation; false to convert from
- * localized to standard notation.
- * @return The pattern expressed in the other notation.
- * @deprecated ICU 59 This method is provided for backwards compatibility and should not be used
- * in any new code.
- */
- @Deprecated
- public static String convertLocalized(
- CharSequence input, DecimalFormatSymbols symbols, boolean toLocalized) {
- if (input == null) return null;
-
- /// This is not the prettiest function in the world, but it gets the job done. ///
-
- // Construct a table of code points to be converted between localized and standard.
- int[][] table = new int[6][2];
- int standIdx = toLocalized ? 0 : 1;
- int localIdx = toLocalized ? 1 : 0;
- table[0][standIdx] = '%';
- table[0][localIdx] = symbols.getPercent();
- table[1][standIdx] = '‰';
- table[1][localIdx] = symbols.getPerMill();
- table[2][standIdx] = '.';
- table[2][localIdx] = symbols.getDecimalSeparator();
- table[3][standIdx] = ',';
- table[3][localIdx] = symbols.getGroupingSeparator();
- table[4][standIdx] = '-';
- table[4][localIdx] = symbols.getMinusSign();
- table[5][standIdx] = '+';
- table[5][localIdx] = symbols.getPlusSign();
-
- // Special case: localIdx characters are NOT allowed to be quotes, like in de_CH.
- // Use '’' instead.
- for (int i = 0; i < table.length; i++) {
- if (table[i][localIdx] == '\'') {
- table[i][localIdx] = '’';
- }
- }
-
- // Iterate through the string and convert
- int offset = 0;
- int state = 0;
- StringBuilder result = new StringBuilder();
- for (; offset < input.length(); ) {
- int cp = Character.codePointAt(input, offset);
- int cpToAppend = cp;
-
- if (state == 1 || state == 3 || state == 4) {
- // Inside user-specified quote
- if (cp == '\'') {
- if (state == 1) {
- state = 0;
- } else if (state == 3) {
- state = 2;
- cpToAppend = -1;
- } else {
- state = 2;
- }
- }
- } else {
- // Base state or inside special character quote
- if (cp == '\'') {
- if (state == 2 && offset + 1 < input.length()) {
- int nextCp = Character.codePointAt(input, offset + 1);
- if (nextCp == '\'') {
- // escaped quote
- state = 4;
- } else {
- // begin user-specified quote sequence
- // we are already in a quote sequence, so omit the opening quote
- state = 3;
- cpToAppend = -1;
- }
- } else {
- state = 1;
- }
- } else {
- boolean needsSpecialQuote = false;
- for (int i = 0; i < table.length; i++) {
- if (table[i][0] == cp) {
- cpToAppend = table[i][1];
- needsSpecialQuote = false; // in case an earlier translation triggered it
- break;
- } else if (table[i][1] == cp) {
- needsSpecialQuote = true;
- }
- }
- if (state == 0 && needsSpecialQuote) {
- state = 2;
- result.appendCodePoint('\'');
- } else if (state == 2 && !needsSpecialQuote) {
- state = 0;
- result.appendCodePoint('\'');
- }
- }
- }
- if (cpToAppend != -1) {
- result.appendCodePoint(cpToAppend);
- }
- offset += Character.charCount(cp);
- }
- if (state == 2) {
- result.appendCodePoint('\'');
- }
- return result.toString();
- }
-
- /** Implements a recursive descent parser for decimal format patterns. */
- static class LdmlDecimalPatternParser {
-
- /**
- * An internal, intermediate data structure used for storing parse results before they are
- * finalized into a DecimalFormatPattern.Builder.
- */
- private static class PatternParseResult {
- SubpatternParseResult positive = new SubpatternParseResult();
- SubpatternParseResult negative = null;
-
- /** Finalizes the temporary data stored in the PatternParseResult to the Builder. */
- void saveToProperties(Properties properties, boolean ignoreRounding) {
- // Translate from PatternState to Properties.
- // Note that most data from "negative" is ignored per the specification of DecimalFormat.
-
- // Grouping settings
- if (positive.groupingSizes[1] != -1) {
- properties.setGroupingSize(positive.groupingSizes[0]);
- } else {
- properties.setGroupingSize(Properties.DEFAULT_GROUPING_SIZE);
- }
- if (positive.groupingSizes[2] != -1) {
- properties.setSecondaryGroupingSize(positive.groupingSizes[1]);
- } else {
- properties.setSecondaryGroupingSize(Properties.DEFAULT_SECONDARY_GROUPING_SIZE);
- }
-
- // For backwards compatibility, require that the pattern emit at least one min digit.
- int minInt, minFrac;
- if (positive.totalIntegerDigits == 0 && positive.maximumFractionDigits > 0) {
- // patterns like ".##"
- minInt = 0;
- minFrac = Math.max(1, positive.minimumFractionDigits);
- } else if (positive.minimumIntegerDigits == 0 && positive.minimumFractionDigits == 0) {
- // patterns like "#.##"
- minInt = 1;
- minFrac = 0;
- } else {
- minInt = positive.minimumIntegerDigits;
- minFrac = positive.minimumFractionDigits;
- }
-
- // Rounding settings
- // Don't set basic rounding when there is a currency sign; defer to CurrencyUsage
- if (positive.minimumSignificantDigits > 0) {
- properties.setMinimumFractionDigits(Properties.DEFAULT_MINIMUM_FRACTION_DIGITS);
- properties.setMaximumFractionDigits(Properties.DEFAULT_MAXIMUM_FRACTION_DIGITS);
- properties.setRoundingIncrement(Properties.DEFAULT_ROUNDING_INCREMENT);
- properties.setMinimumSignificantDigits(positive.minimumSignificantDigits);
- properties.setMaximumSignificantDigits(positive.maximumSignificantDigits);
- } else if (!positive.rounding.isZero()) {
- if (!ignoreRounding) {
- properties.setMinimumFractionDigits(minFrac);
- properties.setMaximumFractionDigits(positive.maximumFractionDigits);
- properties.setRoundingIncrement(positive.rounding.toBigDecimal());
- } else {
- properties.setMinimumFractionDigits(Properties.DEFAULT_MINIMUM_FRACTION_DIGITS);
- properties.setMaximumFractionDigits(Properties.DEFAULT_MAXIMUM_FRACTION_DIGITS);
- properties.setRoundingIncrement(Properties.DEFAULT_ROUNDING_INCREMENT);
- }
- properties.setMinimumSignificantDigits(Properties.DEFAULT_MINIMUM_SIGNIFICANT_DIGITS);
- properties.setMaximumSignificantDigits(Properties.DEFAULT_MAXIMUM_SIGNIFICANT_DIGITS);
- } else {
- if (!ignoreRounding) {
- properties.setMinimumFractionDigits(minFrac);
- properties.setMaximumFractionDigits(positive.maximumFractionDigits);
- properties.setRoundingIncrement(Properties.DEFAULT_ROUNDING_INCREMENT);
- } else {
- properties.setMinimumFractionDigits(Properties.DEFAULT_MINIMUM_FRACTION_DIGITS);
- properties.setMaximumFractionDigits(Properties.DEFAULT_MAXIMUM_FRACTION_DIGITS);
- properties.setRoundingIncrement(Properties.DEFAULT_ROUNDING_INCREMENT);
- }
- properties.setMinimumSignificantDigits(Properties.DEFAULT_MINIMUM_SIGNIFICANT_DIGITS);
- properties.setMaximumSignificantDigits(Properties.DEFAULT_MAXIMUM_SIGNIFICANT_DIGITS);
- }
-
- // If the pattern ends with a '.' then force the decimal point.
- if (positive.hasDecimal && positive.maximumFractionDigits == 0) {
- properties.setDecimalSeparatorAlwaysShown(true);
- } else {
- properties.setDecimalSeparatorAlwaysShown(false);
- }
-
- // Scientific notation settings
- if (positive.exponentDigits > 0) {
- properties.setExponentSignAlwaysShown(positive.exponentShowPlusSign);
- properties.setMinimumExponentDigits(positive.exponentDigits);
- if (positive.minimumSignificantDigits == 0) {
- // patterns without '@' can define max integer digits, used for engineering notation
- properties.setMinimumIntegerDigits(positive.minimumIntegerDigits);
- properties.setMaximumIntegerDigits(positive.totalIntegerDigits);
- } else {
- // patterns with '@' cannot define max integer digits
- properties.setMinimumIntegerDigits(1);
- properties.setMaximumIntegerDigits(Properties.DEFAULT_MAXIMUM_INTEGER_DIGITS);
- }
- } else {
- properties.setExponentSignAlwaysShown(Properties.DEFAULT_EXPONENT_SIGN_ALWAYS_SHOWN);
- properties.setMinimumExponentDigits(Properties.DEFAULT_MINIMUM_EXPONENT_DIGITS);
- properties.setMinimumIntegerDigits(minInt);
- properties.setMaximumIntegerDigits(Properties.DEFAULT_MAXIMUM_INTEGER_DIGITS);
- }
-
- // Padding settings
- if (positive.padding.length() > 0) {
- // The width of the positive prefix and suffix templates are included in the padding
- int paddingWidth =
- positive.paddingWidth
- + AffixPatternUtils.unescapedLength(positive.prefix)
- + AffixPatternUtils.unescapedLength(positive.suffix);
- properties.setFormatWidth(paddingWidth);
- if (positive.padding.length() == 1) {
- properties.setPadString(positive.padding.toString());
- } else if (positive.padding.length() == 2) {
- if (positive.padding.charAt(0) == '\'') {
- properties.setPadString("'");
- } else {
- properties.setPadString(positive.padding.toString());
- }
- } else {
- properties.setPadString(
- positive.padding.subSequence(1, positive.padding.length() - 1).toString());
- }
- assert positive.paddingLocation != null;
- properties.setPadPosition(positive.paddingLocation);
- } else {
- properties.setFormatWidth(Properties.DEFAULT_FORMAT_WIDTH);
- properties.setPadString(Properties.DEFAULT_PAD_STRING);
- properties.setPadPosition(Properties.DEFAULT_PAD_POSITION);
- }
-
- // Set the affixes
- // Always call the setter, even if the prefixes are empty, especially in the case of the
- // negative prefix pattern, to prevent default values from overriding the pattern.
- properties.setPositivePrefixPattern(positive.prefix.toString());
- properties.setPositiveSuffixPattern(positive.suffix.toString());
- if (negative != null) {
- properties.setNegativePrefixPattern(negative.prefix.toString());
- properties.setNegativeSuffixPattern(negative.suffix.toString());
- } else {
- properties.setNegativePrefixPattern(null);
- properties.setNegativeSuffixPattern(null);
- }
-
- // Set the magnitude multiplier
- if (positive.hasPercentSign) {
- properties.setMagnitudeMultiplier(2);
- } else if (positive.hasPerMilleSign) {
- properties.setMagnitudeMultiplier(3);
- } else {
- properties.setMagnitudeMultiplier(Properties.DEFAULT_MAGNITUDE_MULTIPLIER);
- }
- }
- }
-
- private static class SubpatternParseResult {
- int[] groupingSizes = new int[] {0, -1, -1};
- int minimumIntegerDigits = 0;
- int totalIntegerDigits = 0;
- int minimumFractionDigits = 0;
- int maximumFractionDigits = 0;
- int minimumSignificantDigits = 0;
- int maximumSignificantDigits = 0;
- boolean hasDecimal = false;
- int paddingWidth = 0;
- PadPosition paddingLocation = null;
- FormatQuantity4 rounding = new FormatQuantity4();
- boolean exponentShowPlusSign = false;
- int exponentDigits = 0;
- boolean hasPercentSign = false;
- boolean hasPerMilleSign = false;
- boolean hasCurrencySign = false;
-
- StringBuilder padding = new StringBuilder();
- StringBuilder prefix = new StringBuilder();
- StringBuilder suffix = new StringBuilder();
- }
-
- /** An internal class used for tracking the cursor during parsing of a pattern string. */
- private static class ParserState {
- final String pattern;
- int offset;
-
- ParserState(String pattern) {
- this.pattern = pattern;
- this.offset = 0;
- }
-
- int peek() {
- if (offset == pattern.length()) {
- return -1;
- } else {
- return pattern.codePointAt(offset);
- }
- }
-
- int next() {
- int codePoint = peek();
- offset += Character.charCount(codePoint);
- return codePoint;
- }
-
- IllegalArgumentException toParseException(String message) {
- StringBuilder sb = new StringBuilder();
- sb.append("Unexpected character in decimal format pattern: '");
- sb.append(pattern);
- sb.append("': ");
- sb.append(message);
- sb.append(": ");
- if (peek() == -1) {
- sb.append("EOL");
- } else {
- sb.append("'");
- sb.append(Character.toChars(peek()));
- sb.append("'");
- }
- return new IllegalArgumentException(sb.toString());
- }
- }
-
- static void parse(String pattern, Properties properties, boolean ignoreRounding) {
- if (pattern == null || pattern.length() == 0) {
- // Backwards compatibility requires that we reset to the default values.
- // TODO: Only overwrite the properties that "saveToProperties" normally touches?
- properties.clear();
- return;
- }
-
- // TODO: Use whitespace characters from PatternProps
- // TODO: Use thread locals here.
- ParserState state = new ParserState(pattern);
- PatternParseResult result = new PatternParseResult();
- consumePattern(state, result);
- result.saveToProperties(properties, ignoreRounding);
- }
-
- private static void consumePattern(ParserState state, PatternParseResult result) {
- // pattern := subpattern (';' subpattern)?
- consumeSubpattern(state, result.positive);
- if (state.peek() == ';') {
- state.next(); // consume the ';'
- result.negative = new SubpatternParseResult();
- consumeSubpattern(state, result.negative);
- }
- if (state.peek() != -1) {
- throw state.toParseException("pattern");
- }
- }
-
- private static void consumeSubpattern(ParserState state, SubpatternParseResult result) {
- // subpattern := literals? number exponent? literals?
- consumePadding(state, result, PadPosition.BEFORE_PREFIX);
- consumeAffix(state, result, result.prefix);
- consumePadding(state, result, PadPosition.AFTER_PREFIX);
- consumeFormat(state, result);
- consumeExponent(state, result);
- consumePadding(state, result, PadPosition.BEFORE_SUFFIX);
- consumeAffix(state, result, result.suffix);
- consumePadding(state, result, PadPosition.AFTER_SUFFIX);
- }
-
- private static void consumePadding(
- ParserState state, SubpatternParseResult result, PadPosition paddingLocation) {
- if (state.peek() != '*') {
- return;
- }
- result.paddingLocation = paddingLocation;
- state.next(); // consume the '*'
- consumeLiteral(state, result.padding);
- }
-
- private static void consumeAffix(
- ParserState state, SubpatternParseResult result, StringBuilder destination) {
- // literals := { literal }
- while (true) {
- switch (state.peek()) {
- case '#':
- case '@':
- case ';':
- case '*':
- case '.':
- case ',':
- case '0':
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- case '8':
- case '9':
- case -1:
- // Characters that cannot appear unquoted in a literal
- return;
-
- case '%':
- result.hasPercentSign = true;
- break;
-
- case '‰':
- result.hasPerMilleSign = true;
- break;
-
- case '¤':
- result.hasCurrencySign = true;
- break;
- }
- consumeLiteral(state, destination);
- }
- }
-
- private static void consumeLiteral(ParserState state, StringBuilder destination) {
- if (state.peek() == -1) {
- throw state.toParseException("expected unquoted literal but found end of string");
- } else if (state.peek() == '\'') {
- destination.appendCodePoint(state.next()); // consume the starting quote
- while (state.peek() != '\'') {
- if (state.peek() == -1) {
- throw state.toParseException("expected quoted literal but found end of string");
- } else {
- destination.appendCodePoint(state.next()); // consume a quoted character
- }
- }
- destination.appendCodePoint(state.next()); // consume the ending quote
- } else {
- // consume a non-quoted literal character
- destination.appendCodePoint(state.next());
- }
- }
-
- private static void consumeFormat(ParserState state, SubpatternParseResult result) {
- consumeIntegerFormat(state, result);
- if (state.peek() == '.') {
- state.next(); // consume the decimal point
- result.hasDecimal = true;
- result.paddingWidth += 1;
- consumeFractionFormat(state, result);
- }
- }
-
- private static void consumeIntegerFormat(ParserState state, SubpatternParseResult result) {
- boolean seenSignificantDigitMarker = false;
- boolean seenDigit = false;
-
- while (true) {
- switch (state.peek()) {
- case ',':
- result.paddingWidth += 1;
- result.groupingSizes[2] = result.groupingSizes[1];
- result.groupingSizes[1] = result.groupingSizes[0];
- result.groupingSizes[0] = 0;
- break;
-
- case '#':
- if (seenDigit) throw state.toParseException("# cannot follow 0 before decimal point");
- result.paddingWidth += 1;
- result.groupingSizes[0] += 1;
- result.totalIntegerDigits += (seenSignificantDigitMarker ? 0 : 1);
- // no change to result.minimumIntegerDigits
- // no change to result.minimumSignificantDigits
- result.maximumSignificantDigits += (seenSignificantDigitMarker ? 1 : 0);
- result.rounding.appendDigit((byte) 0, 0, true);
- break;
-
- case '@':
- seenSignificantDigitMarker = true;
- if (seenDigit) throw state.toParseException("Can't mix @ and 0 in pattern");
- result.paddingWidth += 1;
- result.groupingSizes[0] += 1;
- result.totalIntegerDigits += 1;
- // no change to result.minimumIntegerDigits
- result.minimumSignificantDigits += 1;
- result.maximumSignificantDigits += 1;
- result.rounding.appendDigit((byte) 0, 0, true);
- break;
-
- case '0':
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- case '8':
- case '9':
- seenDigit = true;
- if (seenSignificantDigitMarker)
- throw state.toParseException("Can't mix @ and 0 in pattern");
- // TODO: Crash here if we've seen the significant digit marker? See NumberFormatTestCases.txt
- result.paddingWidth += 1;
- result.groupingSizes[0] += 1;
- result.totalIntegerDigits += 1;
- result.minimumIntegerDigits += 1;
- // no change to result.minimumSignificantDigits
- result.maximumSignificantDigits += (seenSignificantDigitMarker ? 1 : 0);
- result.rounding.appendDigit((byte) (state.peek() - '0'), 0, true);
- break;
-
- default:
- return;
- }
- state.next(); // consume the symbol
- }
- }
-
- private static void consumeFractionFormat(ParserState state, SubpatternParseResult result) {
- int zeroCounter = 0;
- boolean seenHash = false;
- while (true) {
- switch (state.peek()) {
- case '#':
- seenHash = true;
- result.paddingWidth += 1;
- // no change to result.minimumFractionDigits
- result.maximumFractionDigits += 1;
- zeroCounter++;
- break;
-
- case '0':
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- case '8':
- case '9':
- if (seenHash) throw state.toParseException("0 cannot follow # after decimal point");
- result.paddingWidth += 1;
- result.minimumFractionDigits += 1;
- result.maximumFractionDigits += 1;
- if (state.peek() == '0') {
- zeroCounter++;
- } else {
- result.rounding.appendDigit((byte) (state.peek() - '0'), zeroCounter, false);
- zeroCounter = 0;
- }
- break;
-
- default:
- return;
- }
- state.next(); // consume the symbol
- }
- }
-
- private static void consumeExponent(ParserState state, SubpatternParseResult result) {
- if (state.peek() != 'E') {
- return;
- }
- state.next(); // consume the E
- result.paddingWidth++;
- if (state.peek() == '+') {
- state.next(); // consume the +
- result.exponentShowPlusSign = true;
- result.paddingWidth++;
- }
- while (state.peek() == '0') {
- state.next(); // consume the 0
- result.exponentDigits += 1;
- result.paddingWidth++;
- }
- }
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number;
-
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.io.Serializable;
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-import java.math.BigDecimal;
-import java.math.MathContext;
-import java.math.RoundingMode;
-import java.util.ArrayList;
-
-import com.ibm.icu.impl.number.Parse.ParseMode;
-import com.ibm.icu.impl.number.formatters.BigDecimalMultiplier;
-import com.ibm.icu.impl.number.formatters.CompactDecimalFormat;
-import com.ibm.icu.impl.number.formatters.CurrencyFormat;
-import com.ibm.icu.impl.number.formatters.CurrencyFormat.CurrencyStyle;
-import com.ibm.icu.impl.number.formatters.MagnitudeMultiplier;
-import com.ibm.icu.impl.number.formatters.MeasureFormat;
-import com.ibm.icu.impl.number.formatters.PaddingFormat;
-import com.ibm.icu.impl.number.formatters.PaddingFormat.PadPosition;
-import com.ibm.icu.impl.number.formatters.PositiveDecimalFormat;
-import com.ibm.icu.impl.number.formatters.PositiveNegativeAffixFormat;
-import com.ibm.icu.impl.number.formatters.ScientificFormat;
-import com.ibm.icu.impl.number.rounders.IncrementRounder;
-import com.ibm.icu.impl.number.rounders.MagnitudeRounder;
-import com.ibm.icu.impl.number.rounders.SignificantDigitsRounder;
-import com.ibm.icu.impl.number.rounders.SignificantDigitsRounder.SignificantDigitsMode;
-import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
-import com.ibm.icu.text.CurrencyPluralInfo;
-import com.ibm.icu.text.MeasureFormat.FormatWidth;
-import com.ibm.icu.util.Currency;
-import com.ibm.icu.util.Currency.CurrencyUsage;
-import com.ibm.icu.util.MeasureUnit;
-
-public class Properties
- implements Cloneable,
- Serializable,
- PositiveDecimalFormat.IProperties,
- PositiveNegativeAffixFormat.IProperties,
- MagnitudeMultiplier.IProperties,
- ScientificFormat.IProperties,
- MeasureFormat.IProperties,
- CompactDecimalFormat.IProperties,
- PaddingFormat.IProperties,
- BigDecimalMultiplier.IProperties,
- CurrencyFormat.IProperties,
- Parse.IProperties,
- IncrementRounder.IProperties,
- MagnitudeRounder.IProperties,
- SignificantDigitsRounder.IProperties {
-
- private static final Properties DEFAULT = new Properties();
-
- /** Auto-generated. */
- private static final long serialVersionUID = 4095518955889349243L;
-
- // The setters in this class should NOT have any side-effects or perform any validation. It is
- // up to the consumer of the property bag to deal with property validation.
-
- // The fields are all marked "transient" because custom serialization is being used.
-
- /*--------------------------------------------------------------------------------------------+/
- /| IMPORTANT! |/
- /| WHEN ADDING A NEW PROPERTY, add it here, in #_clear(), in #_copyFrom(), in #equals(), |/
- /| and in #_hashCode(). |/
- /| |/
- /| The unit test PropertiesTest will catch if you forget to add it to #clear(), #copyFrom(), |/
- /| or #equals(), but it will NOT catch if you forget to add it to #hashCode(). |/
- /+--------------------------------------------------------------------------------------------*/
-
- private transient CompactStyle compactStyle;
- private transient Currency currency;
- private transient CurrencyPluralInfo currencyPluralInfo;
- private transient CurrencyStyle currencyStyle;
- private transient CurrencyUsage currencyUsage;
- private transient boolean decimalPatternMatchRequired;
- private transient boolean decimalSeparatorAlwaysShown;
- private transient boolean exponentSignAlwaysShown;
- private transient int formatWidth;
- private transient int groupingSize;
- private transient int magnitudeMultiplier;
- private transient MathContext mathContext;
- private transient int maximumFractionDigits;
- private transient int maximumIntegerDigits;
- private transient int maximumSignificantDigits;
- private transient FormatWidth measureFormatWidth;
- private transient MeasureUnit measureUnit;
- private transient int minimumExponentDigits;
- private transient int minimumFractionDigits;
- private transient int minimumGroupingDigits;
- private transient int minimumIntegerDigits;
- private transient int minimumSignificantDigits;
- private transient BigDecimal multiplier;
- private transient String negativePrefix;
- private transient String negativePrefixPattern;
- private transient String negativeSuffix;
- private transient String negativeSuffixPattern;
- private transient PadPosition padPosition;
- private transient String padString;
- private transient boolean parseCaseSensitive;
- private transient boolean parseNoExponent;
- private transient boolean parseIntegerOnly;
- private transient ParseMode parseMode;
- private transient boolean parseToBigDecimal;
- private transient boolean plusSignAlwaysShown;
- private transient String positivePrefix;
- private transient String positivePrefixPattern;
- private transient String positiveSuffix;
- private transient String positiveSuffixPattern;
- private transient BigDecimal roundingIncrement;
- private transient RoundingMode roundingMode;
- private transient int secondaryGroupingSize;
- private transient SignificantDigitsMode significantDigitsMode;
-
- /*--------------------------------------------------------------------------------------------+/
- /| IMPORTANT! |/
- /| WHEN ADDING A NEW PROPERTY, add it here, in #_clear(), in #_copyFrom(), in #equals(), |/
- /| and in #_hashCode(). |/
- /| |/
- /| The unit test PropertiesTest will catch if you forget to add it to #clear(), #copyFrom(), |/
- /| or #equals(), but it will NOT catch if you forget to add it to #hashCode(). |/
- /+--------------------------------------------------------------------------------------------*/
-
- public Properties() {
- clear();
- }
-
- private Properties _clear() {
- compactStyle = DEFAULT_COMPACT_STYLE;
- currency = DEFAULT_CURRENCY;
- currencyPluralInfo = DEFAULT_CURRENCY_PLURAL_INFO;
- currencyStyle = DEFAULT_CURRENCY_STYLE;
- currencyUsage = DEFAULT_CURRENCY_USAGE;
- decimalPatternMatchRequired = DEFAULT_DECIMAL_PATTERN_MATCH_REQUIRED;
- decimalSeparatorAlwaysShown = DEFAULT_DECIMAL_SEPARATOR_ALWAYS_SHOWN;
- exponentSignAlwaysShown = DEFAULT_EXPONENT_SIGN_ALWAYS_SHOWN;
- formatWidth = DEFAULT_FORMAT_WIDTH;
- groupingSize = DEFAULT_GROUPING_SIZE;
- magnitudeMultiplier = DEFAULT_MAGNITUDE_MULTIPLIER;
- mathContext = DEFAULT_MATH_CONTEXT;
- maximumFractionDigits = DEFAULT_MAXIMUM_FRACTION_DIGITS;
- maximumIntegerDigits = DEFAULT_MAXIMUM_INTEGER_DIGITS;
- maximumSignificantDigits = DEFAULT_MAXIMUM_SIGNIFICANT_DIGITS;
- measureFormatWidth = DEFAULT_MEASURE_FORMAT_WIDTH;
- measureUnit = DEFAULT_MEASURE_UNIT;
- minimumExponentDigits = DEFAULT_MINIMUM_EXPONENT_DIGITS;
- minimumFractionDigits = DEFAULT_MINIMUM_FRACTION_DIGITS;
- minimumGroupingDigits = DEFAULT_MINIMUM_GROUPING_DIGITS;
- minimumIntegerDigits = DEFAULT_MINIMUM_INTEGER_DIGITS;
- minimumSignificantDigits = DEFAULT_MINIMUM_SIGNIFICANT_DIGITS;
- multiplier = DEFAULT_MULTIPLIER;
- negativePrefix = DEFAULT_NEGATIVE_PREFIX;
- negativePrefixPattern = DEFAULT_NEGATIVE_PREFIX_PATTERN;
- negativeSuffix = DEFAULT_NEGATIVE_SUFFIX;
- negativeSuffixPattern = DEFAULT_NEGATIVE_SUFFIX_PATTERN;
- padPosition = DEFAULT_PAD_POSITION;
- padString = DEFAULT_PAD_STRING;
- parseCaseSensitive = DEFAULT_PARSE_CASE_SENSITIVE;
- parseIntegerOnly = DEFAULT_PARSE_INTEGER_ONLY;
- parseMode = DEFAULT_PARSE_MODE;
- parseNoExponent = DEFAULT_PARSE_NO_EXPONENT;
- parseToBigDecimal = DEFAULT_PARSE_TO_BIG_DECIMAL;
- plusSignAlwaysShown = DEFAULT_PLUS_SIGN_ALWAYS_SHOWN;
- positivePrefix = DEFAULT_POSITIVE_PREFIX;
- positivePrefixPattern = DEFAULT_POSITIVE_PREFIX_PATTERN;
- positiveSuffix = DEFAULT_POSITIVE_SUFFIX;
- positiveSuffixPattern = DEFAULT_POSITIVE_SUFFIX_PATTERN;
- roundingIncrement = DEFAULT_ROUNDING_INCREMENT;
- roundingMode = DEFAULT_ROUNDING_MODE;
- secondaryGroupingSize = DEFAULT_SECONDARY_GROUPING_SIZE;
- significantDigitsMode = DEFAULT_SIGNIFICANT_DIGITS_MODE;
- return this;
- }
-
- private Properties _copyFrom(Properties other) {
- compactStyle = other.compactStyle;
- currency = other.currency;
- currencyPluralInfo = other.currencyPluralInfo;
- currencyStyle = other.currencyStyle;
- currencyUsage = other.currencyUsage;
- decimalPatternMatchRequired = other.decimalPatternMatchRequired;
- decimalSeparatorAlwaysShown = other.decimalSeparatorAlwaysShown;
- exponentSignAlwaysShown = other.exponentSignAlwaysShown;
- formatWidth = other.formatWidth;
- groupingSize = other.groupingSize;
- magnitudeMultiplier = other.magnitudeMultiplier;
- mathContext = other.mathContext;
- maximumFractionDigits = other.maximumFractionDigits;
- maximumIntegerDigits = other.maximumIntegerDigits;
- maximumSignificantDigits = other.maximumSignificantDigits;
- measureFormatWidth = other.measureFormatWidth;
- measureUnit = other.measureUnit;
- minimumExponentDigits = other.minimumExponentDigits;
- minimumFractionDigits = other.minimumFractionDigits;
- minimumGroupingDigits = other.minimumGroupingDigits;
- minimumIntegerDigits = other.minimumIntegerDigits;
- minimumSignificantDigits = other.minimumSignificantDigits;
- multiplier = other.multiplier;
- negativePrefix = other.negativePrefix;
- negativePrefixPattern = other.negativePrefixPattern;
- negativeSuffix = other.negativeSuffix;
- negativeSuffixPattern = other.negativeSuffixPattern;
- padPosition = other.padPosition;
- padString = other.padString;
- parseCaseSensitive = other.parseCaseSensitive;
- parseIntegerOnly = other.parseIntegerOnly;
- parseMode = other.parseMode;
- parseNoExponent = other.parseNoExponent;
- parseToBigDecimal = other.parseToBigDecimal;
- plusSignAlwaysShown = other.plusSignAlwaysShown;
- positivePrefix = other.positivePrefix;
- positivePrefixPattern = other.positivePrefixPattern;
- positiveSuffix = other.positiveSuffix;
- positiveSuffixPattern = other.positiveSuffixPattern;
- roundingIncrement = other.roundingIncrement;
- roundingMode = other.roundingMode;
- secondaryGroupingSize = other.secondaryGroupingSize;
- significantDigitsMode = other.significantDigitsMode;
- return this;
- }
-
- private boolean _equals(Properties other) {
- boolean eq = true;
- eq = eq && _equalsHelper(compactStyle, other.compactStyle);
- eq = eq && _equalsHelper(currency, other.currency);
- eq = eq && _equalsHelper(currencyPluralInfo, other.currencyPluralInfo);
- eq = eq && _equalsHelper(currencyStyle, other.currencyStyle);
- eq = eq && _equalsHelper(currencyUsage, other.currencyUsage);
- eq = eq && _equalsHelper(decimalPatternMatchRequired, other.decimalPatternMatchRequired);
- eq = eq && _equalsHelper(decimalSeparatorAlwaysShown, other.decimalSeparatorAlwaysShown);
- eq = eq && _equalsHelper(exponentSignAlwaysShown, other.exponentSignAlwaysShown);
- eq = eq && _equalsHelper(formatWidth, other.formatWidth);
- eq = eq && _equalsHelper(groupingSize, other.groupingSize);
- eq = eq && _equalsHelper(magnitudeMultiplier, other.magnitudeMultiplier);
- eq = eq && _equalsHelper(mathContext, other.mathContext);
- eq = eq && _equalsHelper(maximumFractionDigits, other.maximumFractionDigits);
- eq = eq && _equalsHelper(maximumIntegerDigits, other.maximumIntegerDigits);
- eq = eq && _equalsHelper(maximumSignificantDigits, other.maximumSignificantDigits);
- eq = eq && _equalsHelper(measureFormatWidth, other.measureFormatWidth);
- eq = eq && _equalsHelper(measureUnit, other.measureUnit);
- eq = eq && _equalsHelper(minimumExponentDigits, other.minimumExponentDigits);
- eq = eq && _equalsHelper(minimumFractionDigits, other.minimumFractionDigits);
- eq = eq && _equalsHelper(minimumGroupingDigits, other.minimumGroupingDigits);
- eq = eq && _equalsHelper(minimumIntegerDigits, other.minimumIntegerDigits);
- eq = eq && _equalsHelper(minimumSignificantDigits, other.minimumSignificantDigits);
- eq = eq && _equalsHelper(multiplier, other.multiplier);
- eq = eq && _equalsHelper(negativePrefix, other.negativePrefix);
- eq = eq && _equalsHelper(negativePrefixPattern, other.negativePrefixPattern);
- eq = eq && _equalsHelper(negativeSuffix, other.negativeSuffix);
- eq = eq && _equalsHelper(negativeSuffixPattern, other.negativeSuffixPattern);
- eq = eq && _equalsHelper(padPosition, other.padPosition);
- eq = eq && _equalsHelper(padString, other.padString);
- eq = eq && _equalsHelper(parseCaseSensitive, other.parseCaseSensitive);
- eq = eq && _equalsHelper(parseIntegerOnly, other.parseIntegerOnly);
- eq = eq && _equalsHelper(parseMode, other.parseMode);
- eq = eq && _equalsHelper(parseNoExponent, other.parseNoExponent);
- eq = eq && _equalsHelper(parseToBigDecimal, other.parseToBigDecimal);
- eq = eq && _equalsHelper(plusSignAlwaysShown, other.plusSignAlwaysShown);
- eq = eq && _equalsHelper(positivePrefix, other.positivePrefix);
- eq = eq && _equalsHelper(positivePrefixPattern, other.positivePrefixPattern);
- eq = eq && _equalsHelper(positiveSuffix, other.positiveSuffix);
- eq = eq && _equalsHelper(positiveSuffixPattern, other.positiveSuffixPattern);
- eq = eq && _equalsHelper(roundingIncrement, other.roundingIncrement);
- eq = eq && _equalsHelper(roundingMode, other.roundingMode);
- eq = eq && _equalsHelper(secondaryGroupingSize, other.secondaryGroupingSize);
- eq = eq && _equalsHelper(significantDigitsMode, other.significantDigitsMode);
- return eq;
- }
-
- private boolean _equalsHelper(boolean mine, boolean theirs) {
- return mine == theirs;
- }
-
- private boolean _equalsHelper(int mine, int theirs) {
- return mine == theirs;
- }
-
- private boolean _equalsHelper(Object mine, Object theirs) {
- if (mine == theirs) return true;
- if (mine == null) return false;
- return mine.equals(theirs);
- }
-
- private int _hashCode() {
- int hashCode = 0;
- hashCode ^= _hashCodeHelper(compactStyle);
- hashCode ^= _hashCodeHelper(currency);
- hashCode ^= _hashCodeHelper(currencyPluralInfo);
- hashCode ^= _hashCodeHelper(currencyStyle);
- hashCode ^= _hashCodeHelper(currencyUsage);
- hashCode ^= _hashCodeHelper(decimalPatternMatchRequired);
- hashCode ^= _hashCodeHelper(decimalSeparatorAlwaysShown);
- hashCode ^= _hashCodeHelper(exponentSignAlwaysShown);
- hashCode ^= _hashCodeHelper(formatWidth);
- hashCode ^= _hashCodeHelper(groupingSize);
- hashCode ^= _hashCodeHelper(magnitudeMultiplier);
- hashCode ^= _hashCodeHelper(mathContext);
- hashCode ^= _hashCodeHelper(maximumFractionDigits);
- hashCode ^= _hashCodeHelper(maximumIntegerDigits);
- hashCode ^= _hashCodeHelper(maximumSignificantDigits);
- hashCode ^= _hashCodeHelper(measureFormatWidth);
- hashCode ^= _hashCodeHelper(measureUnit);
- hashCode ^= _hashCodeHelper(minimumExponentDigits);
- hashCode ^= _hashCodeHelper(minimumFractionDigits);
- hashCode ^= _hashCodeHelper(minimumGroupingDigits);
- hashCode ^= _hashCodeHelper(minimumIntegerDigits);
- hashCode ^= _hashCodeHelper(minimumSignificantDigits);
- hashCode ^= _hashCodeHelper(multiplier);
- hashCode ^= _hashCodeHelper(negativePrefix);
- hashCode ^= _hashCodeHelper(negativePrefixPattern);
- hashCode ^= _hashCodeHelper(negativeSuffix);
- hashCode ^= _hashCodeHelper(negativeSuffixPattern);
- hashCode ^= _hashCodeHelper(padPosition);
- hashCode ^= _hashCodeHelper(padString);
- hashCode ^= _hashCodeHelper(parseCaseSensitive);
- hashCode ^= _hashCodeHelper(parseIntegerOnly);
- hashCode ^= _hashCodeHelper(parseMode);
- hashCode ^= _hashCodeHelper(parseNoExponent);
- hashCode ^= _hashCodeHelper(parseToBigDecimal);
- hashCode ^= _hashCodeHelper(plusSignAlwaysShown);
- hashCode ^= _hashCodeHelper(positivePrefix);
- hashCode ^= _hashCodeHelper(positivePrefixPattern);
- hashCode ^= _hashCodeHelper(positiveSuffix);
- hashCode ^= _hashCodeHelper(positiveSuffixPattern);
- hashCode ^= _hashCodeHelper(roundingIncrement);
- hashCode ^= _hashCodeHelper(roundingMode);
- hashCode ^= _hashCodeHelper(secondaryGroupingSize);
- hashCode ^= _hashCodeHelper(significantDigitsMode);
- return hashCode;
- }
-
- private int _hashCodeHelper(boolean value) {
- return value ? 1 : 0;
- }
-
- private int _hashCodeHelper(int value) {
- return value * 13;
- }
-
- private int _hashCodeHelper(Object value) {
- if (value == null) return 0;
- return value.hashCode();
- }
-
- public Properties clear() {
- return _clear();
- }
-
- /** Creates and returns a shallow copy of the property bag. */
- @Override
- public Properties clone() {
- // super.clone() returns a shallow copy.
- try {
- return (Properties) super.clone();
- } catch (CloneNotSupportedException e) {
- // Should never happen since super is Object
- throw new UnsupportedOperationException(e);
- }
- }
-
- /**
- * Shallow-copies the properties from the given property bag into this property bag.
- *
- * @param other The property bag from which to copy and which will not be modified.
- * @return The current property bag (the one modified by this operation), for chaining.
- */
- public Properties copyFrom(Properties other) {
- return _copyFrom(other);
- }
-
- @Override
- public boolean equals(Object other) {
- if (other == null) return false;
- if (this == other) return true;
- if (!(other instanceof Properties)) return false;
- return _equals((Properties) other);
- }
-
- @Override
- public CompactStyle getCompactStyle() {
- return compactStyle;
- }
-
- @Override
- public Currency getCurrency() {
- return currency;
- }
-
- /// BEGIN GETTERS/SETTERS ///
-
- @Override
- @Deprecated
- public CurrencyPluralInfo getCurrencyPluralInfo() {
- return currencyPluralInfo;
- }
-
- @Override
- public CurrencyStyle getCurrencyStyle() {
- return currencyStyle;
- }
-
- @Override
- public CurrencyUsage getCurrencyUsage() {
- return currencyUsage;
- }
-
- @Override
- public boolean getDecimalPatternMatchRequired() {
- return decimalPatternMatchRequired;
- }
-
- @Override
- public boolean getDecimalSeparatorAlwaysShown() {
- return decimalSeparatorAlwaysShown;
- }
-
- @Override
- public boolean getExponentSignAlwaysShown() {
- return exponentSignAlwaysShown;
- }
-
- @Override
- public int getFormatWidth() {
- return formatWidth;
- }
-
- @Override
- public int getGroupingSize() {
- return groupingSize;
- }
-
- @Override
- public int getMagnitudeMultiplier() {
- return magnitudeMultiplier;
- }
-
- @Override
- public MathContext getMathContext() {
- return mathContext;
- }
-
- @Override
- public int getMaximumFractionDigits() {
- return maximumFractionDigits;
- }
-
- @Override
- public int getMaximumIntegerDigits() {
- return maximumIntegerDigits;
- }
-
- @Override
- public int getMaximumSignificantDigits() {
- return maximumSignificantDigits;
- }
-
- @Override
- public FormatWidth getMeasureFormatWidth() {
- return measureFormatWidth;
- }
-
- @Override
- public MeasureUnit getMeasureUnit() {
- return measureUnit;
- }
-
- @Override
- public int getMinimumExponentDigits() {
- return minimumExponentDigits;
- }
-
- @Override
- public int getMinimumFractionDigits() {
- return minimumFractionDigits;
- }
-
- @Override
- public int getMinimumGroupingDigits() {
- return minimumGroupingDigits;
- }
-
- @Override
- public int getMinimumIntegerDigits() {
- return minimumIntegerDigits;
- }
-
- @Override
- public int getMinimumSignificantDigits() {
- return minimumSignificantDigits;
- }
-
- @Override
- public BigDecimal getMultiplier() {
- return multiplier;
- }
-
- @Override
- public String getNegativePrefix() {
- return negativePrefix;
- }
-
- @Override
- public String getNegativePrefixPattern() {
- return negativePrefixPattern;
- }
-
- @Override
- public String getNegativeSuffix() {
- return negativeSuffix;
- }
-
- @Override
- public String getNegativeSuffixPattern() {
- return negativeSuffixPattern;
- }
-
- @Override
- public PadPosition getPadPosition() {
- return padPosition;
- }
-
- @Override
- public String getPadString() {
- return padString;
- }
-
- @Override
- public boolean getParseCaseSensitive() {
- return parseCaseSensitive;
- }
-
- @Override
- public boolean getParseIntegerOnly() {
- return parseIntegerOnly;
- }
-
- @Override
- public ParseMode getParseMode() {
- return parseMode;
- }
-
- @Override
- public boolean getParseNoExponent() {
- return parseNoExponent;
- }
-
- @Override
- public boolean getParseToBigDecimal() {
- return parseToBigDecimal;
- }
-
- @Override
- public boolean getPlusSignAlwaysShown() {
- return plusSignAlwaysShown;
- }
-
- @Override
- public String getPositivePrefix() {
- return positivePrefix;
- }
-
- @Override
- public String getPositivePrefixPattern() {
- return positivePrefixPattern;
- }
-
- @Override
- public String getPositiveSuffix() {
- return positiveSuffix;
- }
-
- @Override
- public String getPositiveSuffixPattern() {
- return positiveSuffixPattern;
- }
-
- @Override
- public BigDecimal getRoundingIncrement() {
- return roundingIncrement;
- }
-
- @Override
- public RoundingMode getRoundingMode() {
- return roundingMode;
- }
-
- @Override
- public int getSecondaryGroupingSize() {
- return secondaryGroupingSize;
- }
-
- @Override
- public SignificantDigitsMode getSignificantDigitsMode() {
- return significantDigitsMode;
- }
-
- @Override
- public int hashCode() {
- return _hashCode();
- }
-
- /** Custom serialization: re-create object from serialized properties. */
- private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
- ois.defaultReadObject();
-
- // Initialize to empty
- clear();
-
- // Extra int for possible future use
- ois.readInt();
-
- // 1) How many fields were serialized?
- int count = ois.readInt();
-
- // 2) Read each field by its name and value
- for (int i=0; i<count; i++) {
- String name = (String) ois.readObject();
- Object value = ois.readObject();
-
- // Get the field reference
- Field field = null;
- try {
- field = Properties.class.getDeclaredField(name);
- } catch (NoSuchFieldException e) {
- // The field name does not exist! Possibly corrupted serialization. Ignore this entry.
- continue;
- } catch (SecurityException e) {
- // Should not happen
- throw new AssertionError(e);
- }
-
- // NOTE: If the type of a field were changed in the future, this would be the place to check:
- // If the variable `value` is the old type, perform any conversions necessary.
-
- // Save value into the field
- try {
- field.set(this, value);
- } catch (IllegalArgumentException e) {
- // Should not happen
- throw new AssertionError(e);
- } catch (IllegalAccessException e) {
- // Should not happen
- throw new AssertionError(e);
- }
- }
- }
-
- @Override
- public Properties setCompactStyle(CompactStyle compactStyle) {
- this.compactStyle = compactStyle;
- return this;
- }
-
- @Override
- public Properties setCurrency(Currency currency) {
- this.currency = currency;
- return this;
- }
-
- @Override
- @Deprecated
- public Properties setCurrencyPluralInfo(CurrencyPluralInfo currencyPluralInfo) {
- // TODO: In order to maintain immutability, we have to perform a clone here.
- // It would be better to just retire CurrencyPluralInfo entirely.
- if (currencyPluralInfo != null) {
- currencyPluralInfo = (CurrencyPluralInfo) currencyPluralInfo.clone();
- }
- this.currencyPluralInfo = currencyPluralInfo;
- return this;
- }
-
- @Override
- public Properties setCurrencyStyle(CurrencyStyle currencyStyle) {
- this.currencyStyle = currencyStyle;
- return this;
- }
-
- @Override
- public Properties setCurrencyUsage(CurrencyUsage currencyUsage) {
- this.currencyUsage = currencyUsage;
- return this;
- }
-
- @Override
- public Properties setDecimalPatternMatchRequired(boolean decimalPatternMatchRequired) {
- this.decimalPatternMatchRequired = decimalPatternMatchRequired;
- return this;
- }
-
- @Override
- public Properties setDecimalSeparatorAlwaysShown(boolean alwaysShowDecimal) {
- this.decimalSeparatorAlwaysShown = alwaysShowDecimal;
- return this;
- }
-
- @Override
- public Properties setExponentSignAlwaysShown(boolean exponentSignAlwaysShown) {
- this.exponentSignAlwaysShown = exponentSignAlwaysShown;
- return this;
- }
-
- @Override
- public Properties setFormatWidth(int paddingWidth) {
- this.formatWidth = paddingWidth;
- return this;
- }
-
- @Override
- public Properties setGroupingSize(int groupingSize) {
- this.groupingSize = groupingSize;
- return this;
- }
-
- @Override
- public Properties setMagnitudeMultiplier(int magnitudeMultiplier) {
- this.magnitudeMultiplier = magnitudeMultiplier;
- return this;
- }
-
- @Override
- public Properties setMathContext(MathContext mathContext) {
- this.mathContext = mathContext;
- return this;
- }
-
- @Override
- public Properties setMaximumFractionDigits(int maximumFractionDigits) {
- this.maximumFractionDigits = maximumFractionDigits;
- return this;
- }
-
- @Override
- public Properties setMaximumIntegerDigits(int maximumIntegerDigits) {
- this.maximumIntegerDigits = maximumIntegerDigits;
- return this;
- }
-
- @Override
- public Properties setMaximumSignificantDigits(int maximumSignificantDigits) {
- this.maximumSignificantDigits = maximumSignificantDigits;
- return this;
- }
-
- @Override
- public Properties setMeasureFormatWidth(FormatWidth measureFormatWidth) {
- this.measureFormatWidth = measureFormatWidth;
- return this;
- }
-
- @Override
- public Properties setMeasureUnit(MeasureUnit measureUnit) {
- this.measureUnit = measureUnit;
- return this;
- }
-
- @Override
- public Properties setMinimumExponentDigits(int exponentDigits) {
- this.minimumExponentDigits = exponentDigits;
- return this;
- }
-
- @Override
- public Properties setMinimumFractionDigits(int minimumFractionDigits) {
- this.minimumFractionDigits = minimumFractionDigits;
- return this;
- }
-
- @Override
- public Properties setMinimumGroupingDigits(int minimumGroupingDigits) {
- this.minimumGroupingDigits = minimumGroupingDigits;
- return this;
- }
-
- @Override
- public Properties setMinimumIntegerDigits(int minimumIntegerDigits) {
- this.minimumIntegerDigits = minimumIntegerDigits;
- return this;
- }
-
- @Override
- public Properties setMinimumSignificantDigits(int minimumSignificantDigits) {
- this.minimumSignificantDigits = minimumSignificantDigits;
- return this;
- }
-
- @Override
- public Properties setMultiplier(BigDecimal multiplier) {
- this.multiplier = multiplier;
- return this;
- }
-
- @Override
- public Properties setNegativePrefix(String negativePrefix) {
- this.negativePrefix = negativePrefix;
- return this;
- }
-
- @Override
- public Properties setNegativePrefixPattern(String negativePrefixPattern) {
- this.negativePrefixPattern = negativePrefixPattern;
- return this;
- }
-
- @Override
- public Properties setNegativeSuffix(String negativeSuffix) {
- this.negativeSuffix = negativeSuffix;
- return this;
- }
-
- @Override
- public Properties setNegativeSuffixPattern(String negativeSuffixPattern) {
- this.negativeSuffixPattern = negativeSuffixPattern;
- return this;
- }
-
- @Override
- public Properties setPadPosition(PadPosition paddingLocation) {
- this.padPosition = paddingLocation;
- return this;
- }
-
- @Override
- public Properties setPadString(String paddingString) {
- this.padString = paddingString;
- return this;
- }
-
- @Override
- public Properties setParseCaseSensitive(boolean parseCaseSensitive) {
- this.parseCaseSensitive = parseCaseSensitive;
- return this;
- }
-
- @Override
- public Properties setParseIntegerOnly(boolean parseIntegerOnly) {
- this.parseIntegerOnly = parseIntegerOnly;
- return this;
- }
-
- @Override
- public Properties setParseMode(ParseMode parseMode) {
- this.parseMode = parseMode;
- return this;
- }
-
- @Override
- public Properties setParseNoExponent(boolean parseNoExponent) {
- this.parseNoExponent = parseNoExponent;
- return this;
- }
-
- @Override
- public Properties setParseToBigDecimal(boolean parseToBigDecimal) {
- this.parseToBigDecimal = parseToBigDecimal;
- return this;
- }
-
- @Override
- public Properties setPlusSignAlwaysShown(boolean plusSignAlwaysShown) {
- this.plusSignAlwaysShown = plusSignAlwaysShown;
- return this;
- }
-
- @Override
- public Properties setPositivePrefix(String positivePrefix) {
- this.positivePrefix = positivePrefix;
- return this;
- }
-
- @Override
- public Properties setPositivePrefixPattern(String positivePrefixPattern) {
- this.positivePrefixPattern = positivePrefixPattern;
- return this;
- }
-
- @Override
- public Properties setPositiveSuffix(String positiveSuffix) {
- this.positiveSuffix = positiveSuffix;
- return this;
- }
-
- @Override
- public Properties setPositiveSuffixPattern(String positiveSuffixPattern) {
- this.positiveSuffixPattern = positiveSuffixPattern;
- return this;
- }
-
- @Override
- public Properties setRoundingIncrement(BigDecimal roundingIncrement) {
- this.roundingIncrement = roundingIncrement;
- return this;
- }
-
- @Override
- public Properties setRoundingMode(RoundingMode roundingMode) {
- this.roundingMode = roundingMode;
- return this;
- }
-
- @Override
- public Properties setSecondaryGroupingSize(int secondaryGroupingSize) {
- this.secondaryGroupingSize = secondaryGroupingSize;
- return this;
- }
-
- @Override
- public Properties setSignificantDigitsMode(SignificantDigitsMode significantDigitsMode) {
- this.significantDigitsMode = significantDigitsMode;
- return this;
- }
-
- @Override
- public String toString() {
- StringBuilder result = new StringBuilder();
- result.append("<Properties");
- Field[] fields = Properties.class.getDeclaredFields();
- for (Field field : fields) {
- Object myValue, defaultValue;
- try {
- myValue = field.get(this);
- defaultValue = field.get(DEFAULT);
- } catch (IllegalArgumentException e) {
- e.printStackTrace();
- continue;
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- continue;
- }
- if (myValue == null && defaultValue == null) {
- continue;
- } else if (myValue == null || defaultValue == null) {
- result.append(" " + field.getName() + ":" + myValue);
- } else if (!myValue.equals(defaultValue)) {
- result.append(" " + field.getName() + ":" + myValue);
- }
- }
- result.append(">");
- return result.toString();
- }
-
- /**
- * Custom serialization: save fields along with their name, so that fields can be easily added in
- * the future in any order. Only save fields that differ from their default value.
- */
- private void writeObject(ObjectOutputStream oos) throws IOException {
- oos.defaultWriteObject();
-
- // Extra int for possible future use
- oos.writeInt(0);
-
- ArrayList<Field> fieldsToSerialize = new ArrayList<Field>();
- ArrayList<Object> valuesToSerialize = new ArrayList<Object>();
- Field[] fields = Properties.class.getDeclaredFields();
- for (Field field : fields) {
- if (Modifier.isStatic(field.getModifiers())) {
- continue;
- }
- try {
- Object myValue = field.get(this);
- if (myValue == null) {
- // All *Object* values default to null; no need to serialize.
- continue;
- }
- Object defaultValue = field.get(DEFAULT);
- if (!myValue.equals(defaultValue)) {
- fieldsToSerialize.add(field);
- valuesToSerialize.add(myValue);
- }
- } catch (IllegalArgumentException e) {
- // Should not happen
- throw new AssertionError(e);
- } catch (IllegalAccessException e) {
- // Should not happen
- throw new AssertionError(e);
- }
- }
-
- // 1) How many fields are to be serialized?
- int count = fieldsToSerialize.size();
- oos.writeInt(count);
-
- // 2) Write each field with its name and value
- for (int i = 0; i < count; i++) {
- Field field = fieldsToSerialize.get(i);
- Object value = valuesToSerialize.get(i);
- oos.writeObject(field.getName());
- oos.writeObject(value);
- }
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number;
-
-import java.math.MathContext;
-import java.math.RoundingMode;
-
-import com.ibm.icu.impl.number.formatters.CompactDecimalFormat;
-import com.ibm.icu.impl.number.formatters.ScientificFormat;
-
-/**
- * The base class for a Rounder used by ICU Decimal Format.
- *
- * <p>A Rounder must implement the method {@link #apply}. An implementation must:
- *
- * <ol>
- * <li>Either have the code <code>applyDefaults(input);</code> in its apply function, or otherwise
- * ensure that minFrac, maxFrac, minInt, and maxInt are obeyed, paying special attention to
- * the case when the input is zero.
- * <li>Call one of {@link FormatQuantity#roundToInterval}, {@link
- * FormatQuantity#roundToMagnitude}, or {@link FormatQuantity#roundToInfinity} on the input.
- * </ol>
- *
- * <p>In order to be used by {@link CompactDecimalFormat} and {@link ScientificFormat}, among
- * others, your rounder must be stable upon <em>decreasing</em> the magnitude of the input number.
- * For example, if your rounder converts "999" to "1000", it must also convert "99.9" to "100" and
- * "0.999" to "1". (The opposite does not need to be the case: you can round "0.999" to "1" but keep
- * "999" as "999".)
- *
- * @see com.ibm.icu.impl.number.rounders.MagnitudeRounder
- * @see com.ibm.icu.impl.number.rounders.IncrementRounder
- * @see com.ibm.icu.impl.number.rounders.SignificantDigitsRounder
- * @see com.ibm.icu.impl.number.rounders.NoRounder
- */
-public abstract class Rounder extends Format.BeforeFormat {
-
- public static interface IBasicRoundingProperties {
-
- static int DEFAULT_MINIMUM_INTEGER_DIGITS = -1;
-
- /** @see #setMinimumIntegerDigits */
- public int getMinimumIntegerDigits();
-
- /**
- * Sets the minimum number of digits to display before the decimal point. If the number has
- * fewer than this number of digits, the number will be padded with zeros. The pattern "#00.0#",
- * for example, corresponds to 2 minimum integer digits, and the number 5.3 would be formatted
- * as "05.3" in locale <em>en-US</em>.
- *
- * @param minimumIntegerDigits The minimum number of integer digits to output.
- * @return The property bag, for chaining.
- */
- public IBasicRoundingProperties setMinimumIntegerDigits(int minimumIntegerDigits);
-
- static int DEFAULT_MAXIMUM_INTEGER_DIGITS = -1;
-
- /** @see #setMaximumIntegerDigits */
- public int getMaximumIntegerDigits();
-
- /**
- * Sets the maximum number of digits to display before the decimal point. If the number has more
- * than this number of digits, the extra digits will be truncated. For example, if maximum
- * integer digits is 2, and you attempt to format the number 1970, you will get "70" in locale
- * <em>en-US</em>. It is not possible to specify the maximum integer digits using a pattern
- * string, except in the special case of a scientific format pattern.
- *
- * @param maximumIntegerDigits The maximum number of integer digits to output.
- * @return The property bag, for chaining.
- */
- public IBasicRoundingProperties setMaximumIntegerDigits(int maximumIntegerDigits);
-
- static int DEFAULT_MINIMUM_FRACTION_DIGITS = -1;
-
- /** @see #setMinimumFractionDigits */
- public int getMinimumFractionDigits();
-
- /**
- * Sets the minimum number of digits to display after the decimal point. If the number has fewer
- * than this number of digits, the number will be padded with zeros. The pattern "#00.0#", for
- * example, corresponds to 1 minimum fraction digit, and the number 456 would be formatted as
- * "456.0" in locale <em>en-US</em>.
- *
- * @param minimumFractionDigits The minimum number of fraction digits to output.
- * @return The property bag, for chaining.
- */
- public IBasicRoundingProperties setMinimumFractionDigits(int minimumFractionDigits);
-
- static int DEFAULT_MAXIMUM_FRACTION_DIGITS = -1;
-
- /** @see #setMaximumFractionDigits */
- public int getMaximumFractionDigits();
-
- /**
- * Sets the maximum number of digits to display after the decimal point. If the number has fewer
- * than this number of digits, the number will be rounded off using the rounding mode specified
- * by {@link #setRoundingMode(RoundingMode)}. The pattern "#00.0#", for example, corresponds to
- * 2 maximum fraction digits, and the number 456.789 would be formatted as "456.79" in locale
- * <em>en-US</em> with the default rounding mode. Note that the number 456.999 would be
- * formatted as "457.0" given the same configurations.
- *
- * @param maximumFractionDigits The maximum number of fraction digits to output.
- * @return The property bag, for chaining.
- */
- public IBasicRoundingProperties setMaximumFractionDigits(int maximumFractionDigits);
-
- static RoundingMode DEFAULT_ROUNDING_MODE = null;
-
- /** @see #setRoundingMode */
- public RoundingMode getRoundingMode();
-
- /**
- * Sets the rounding mode, which determines under which conditions extra decimal places are
- * rounded either up or down. See {@link RoundingMode} for details on the choices of rounding
- * mode. The default if not set explicitly is {@link RoundingMode#HALF_EVEN}.
- *
- * <p>This setting is ignored if {@link #setMathContext} is used.
- *
- * @param roundingMode The rounding mode to use when rounding is required.
- * @return The property bag, for chaining.
- * @see RoundingMode
- * @see #setMathContext
- */
- public IBasicRoundingProperties setRoundingMode(RoundingMode roundingMode);
-
- static MathContext DEFAULT_MATH_CONTEXT = null;
-
- /** @see #setMathContext */
- public MathContext getMathContext();
-
- /**
- * Sets the {@link MathContext} to be used during math and rounding operations. A MathContext
- * encapsulates a RoundingMode and the number of significant digits in the output.
- *
- * @param mathContext The math context to use when rounding is required.
- * @return The property bag, for chaining.
- * @see MathContext
- * @see #setRoundingMode
- */
- public IBasicRoundingProperties setMathContext(MathContext mathContext);
- }
-
- public static interface MultiplierGenerator {
- public int getMultiplier(int magnitude);
- }
-
- // Properties available to all rounding strategies
- protected final MathContext mathContext;
- protected final int minInt;
- protected final int maxInt;
- protected final int minFrac;
- protected final int maxFrac;
-
- /**
- * Constructor that uses integer and fraction digit lengths from IBasicRoundingProperties.
- *
- * @param properties
- */
- protected Rounder(IBasicRoundingProperties properties) {
- mathContext = RoundingUtils.getMathContextOrUnlimited(properties);
-
- int _maxInt = properties.getMaximumIntegerDigits();
- int _minInt = properties.getMinimumIntegerDigits();
- int _maxFrac = properties.getMaximumFractionDigits();
- int _minFrac = properties.getMinimumFractionDigits();
-
- // Validate min/max int/frac.
- // For backwards compatibility, minimum overrides maximum if the two conflict.
- // The following logic ensures that there is always a minimum of at least one digit.
- if (_minInt == 0 && _maxFrac != 0) {
- // Force a digit to the right of the decimal point.
- minFrac = _minFrac <= 0 ? 1 : _minFrac;
- maxFrac = _maxFrac < 0 ? Integer.MAX_VALUE : _maxFrac < minFrac ? minFrac : _maxFrac;
- minInt = 0;
- maxInt = _maxInt < 0 ? Integer.MAX_VALUE : _maxInt;
- } else {
- // Force a digit to the left of the decimal point.
- minFrac = _minFrac < 0 ? 0 : _minFrac;
- maxFrac = _maxFrac < 0 ? Integer.MAX_VALUE : _maxFrac < minFrac ? minFrac : _maxFrac;
- minInt = _minInt <= 0 ? 1 : _minInt;
- maxInt = _maxInt < 0 ? Integer.MAX_VALUE : _maxInt < minInt ? minInt : _maxInt;
- }
- }
-
- /**
- * Perform rounding and specification of integer and fraction digit lengths on the input quantity.
- * Calling this method will change the state of the FormatQuantity.
- *
- * @param input The {@link FormatQuantity} to be modified and rounded.
- */
- public abstract void apply(FormatQuantity input);
-
- /**
- * Rounding can affect the magnitude. First we attempt to adjust according to the original
- * magnitude, and if the magnitude changes, we adjust according to a magnitude one greater. Note
- * that this algorithm assumes that increasing the multiplier never increases the number of digits
- * that can be displayed.
- *
- * @param input The quantity to be rounded.
- * @param mg The implementation that returns magnitude adjustment based on a given starting
- * magnitude.
- * @return The multiplier that was chosen to best fit the input.
- */
- public int chooseMultiplierAndApply(FormatQuantity input, MultiplierGenerator mg) {
- FormatQuantity copy = input.clone();
-
- int magnitude = input.getMagnitude();
- int multiplier = mg.getMultiplier(magnitude);
- input.adjustMagnitude(multiplier);
- apply(input);
- if (input.getMagnitude() == magnitude + multiplier + 1) {
- magnitude += 1;
- input.copyFrom(copy);
- multiplier = mg.getMultiplier(magnitude);
- input.adjustMagnitude(multiplier);
- assert input.getMagnitude() == magnitude + multiplier - 1;
- apply(input);
- assert input.getMagnitude() == magnitude + multiplier;
- }
-
- return multiplier;
- }
-
- /**
- * Implementations can call this method to perform default logic for min/max digits. This method
- * performs logic for handling of a zero input.
- *
- * @param input The digits being formatted.
- */
- protected void applyDefaults(FormatQuantity input) {
- input.setIntegerFractionLength(minInt, maxInt, minFrac, maxFrac);
- }
-
- private static final ThreadLocal<Properties> threadLocalProperties =
- new ThreadLocal<Properties>() {
- @Override
- protected Properties initialValue() {
- return new Properties();
- }
- };
-
- /**
- * Gets a thread-local property bag that can be used to deliver properties to a constructor.
- * Rounders themselves are guaranteed to not internally use a copy of this property bag.
- *
- * @return A clean, thread-local property bag.
- */
- public static Properties getThreadLocalProperties() {
- return threadLocalProperties.get().clear();
- }
-
- @Override
- public void before(FormatQuantity input, ModifierHolder mods) {
- apply(input);
- }
-
- @Override
- public void export(Properties properties) {
- properties.setMathContext(mathContext);
- properties.setRoundingMode(mathContext.getRoundingMode());
- properties.setMinimumFractionDigits(minFrac);
- properties.setMinimumIntegerDigits(minInt);
- properties.setMaximumFractionDigits(maxFrac);
- properties.setMaximumIntegerDigits(maxInt);
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number;
-
-import java.math.BigDecimal;
-import java.math.MathContext;
-import java.math.RoundingMode;
-
-import com.ibm.icu.impl.number.Rounder.IBasicRoundingProperties;
-
-/** @author sffc */
-public class RoundingUtils {
-
- public static final int SECTION_LOWER = 1;
- public static final int SECTION_MIDPOINT = 2;
- public static final int SECTION_UPPER = 3;
-
- /**
- * Converts a rounding mode and metadata about the quantity being rounded to a boolean determining
- * whether the value should be rounded toward infinity or toward zero.
- *
- * <p>The parameters are of type int because benchmarks on an x86-64 processor against OpenJDK
- * showed that ints were demonstrably faster than enums in switch statements.
- *
- * @param isEven Whether the digit immediately before the rounding magnitude is even.
- * @param isNegative Whether the quantity is negative.
- * @param section Whether the part of the quantity to the right of the rounding magnitude is
- * exactly halfway between two digits, whether it is in the lower part (closer to zero), or
- * whether it is in the upper part (closer to infinity). See {@link #SECTION_LOWER}, {@link
- * #SECTION_MIDPOINT}, and {@link #SECTION_UPPER}.
- * @param roundingMode The integer version of the {@link RoundingMode}, which you can get via
- * {@link RoundingMode#ordinal}.
- * @param reference A reference object to be used when throwing an ArithmeticException.
- * @return true if the number should be rounded toward zero; false if it should be rounded toward
- * infinity.
- */
- public static boolean getRoundingDirection(
- boolean isEven, boolean isNegative, int section, int roundingMode, Object reference) {
- switch (roundingMode) {
- case BigDecimal.ROUND_UP:
- // round away from zero
- return false;
-
- case BigDecimal.ROUND_DOWN:
- // round toward zero
- return true;
-
- case BigDecimal.ROUND_CEILING:
- // round toward positive infinity
- return isNegative;
-
- case BigDecimal.ROUND_FLOOR:
- // round toward negative infinity
- return !isNegative;
-
- case BigDecimal.ROUND_HALF_UP:
- switch (section) {
- case SECTION_MIDPOINT:
- return false;
- case SECTION_LOWER:
- return true;
- case SECTION_UPPER:
- return false;
- }
- break;
-
- case BigDecimal.ROUND_HALF_DOWN:
- switch (section) {
- case SECTION_MIDPOINT:
- return true;
- case SECTION_LOWER:
- return true;
- case SECTION_UPPER:
- return false;
- }
- break;
-
- case BigDecimal.ROUND_HALF_EVEN:
- switch (section) {
- case SECTION_MIDPOINT:
- return isEven;
- case SECTION_LOWER:
- return true;
- case SECTION_UPPER:
- return false;
- }
- break;
- }
-
- // Rounding mode UNNECESSARY
- throw new ArithmeticException("Rounding is required on " + reference.toString());
- }
-
- /**
- * Gets whether the given rounding mode's rounding boundary is at the midpoint. The rounding
- * boundary is the point at which a number switches from being rounded down to being rounded up.
- * For example, with rounding mode HALF_EVEN, HALF_UP, or HALF_DOWN, the rounding boundary is at
- * the midpoint, and this function would return true. However, for UP, DOWN, CEILING, and FLOOR,
- * the rounding boundary is at the "edge", and this function would return false.
- *
- * @param roundingMode The integer version of the {@link RoundingMode}.
- * @return true if rounding mode is HALF_EVEN, HALF_UP, or HALF_DOWN; false otherwise.
- */
- public static boolean roundsAtMidpoint(int roundingMode) {
- switch (roundingMode) {
- case BigDecimal.ROUND_UP:
- case BigDecimal.ROUND_DOWN:
- case BigDecimal.ROUND_CEILING:
- case BigDecimal.ROUND_FLOOR:
- return false;
-
- default:
- return true;
- }
- }
-
- private static final MathContext[] MATH_CONTEXT_BY_ROUNDING_MODE_UNLIMITED =
- new MathContext[RoundingMode.values().length];
-
- private static final MathContext[] MATH_CONTEXT_BY_ROUNDING_MODE_16_DIGITS =
- new MathContext[RoundingMode.values().length];
-
- static {
- for (int i = 0; i < MATH_CONTEXT_BY_ROUNDING_MODE_16_DIGITS.length; i++) {
- MATH_CONTEXT_BY_ROUNDING_MODE_UNLIMITED[i] = new MathContext(0, RoundingMode.valueOf(i));
- MATH_CONTEXT_BY_ROUNDING_MODE_16_DIGITS[i] = new MathContext(16);
- }
- }
-
- /**
- * Gets the user-specified math context out of the property bag. If there is none, falls back to a
- * math context with unlimited precision and the user-specified rounding mode, which defaults to
- * HALF_EVEN (the IEEE 754R default).
- *
- * @param properties The property bag.
- * @return A {@link MathContext}. Never null.
- */
- public static MathContext getMathContextOrUnlimited(IBasicRoundingProperties properties) {
- MathContext mathContext = properties.getMathContext();
- if (mathContext == null) {
- RoundingMode roundingMode = properties.getRoundingMode();
- if (roundingMode == null) roundingMode = RoundingMode.HALF_EVEN;
- mathContext = MATH_CONTEXT_BY_ROUNDING_MODE_UNLIMITED[roundingMode.ordinal()];
- }
- return mathContext;
- }
-
- /**
- * Gets the user-specified math context out of the property bag. If there is none, falls back to a
- * math context with 16 digits of precision (the 64-bit IEEE 754R default) and the user-specified
- * rounding mode, which defaults to HALF_EVEN (the IEEE 754R default).
- *
- * @param properties The property bag.
- * @return A {@link MathContext}. Never null.
- */
- public static MathContext getMathContextOr16Digits(IBasicRoundingProperties properties) {
- MathContext mathContext = properties.getMathContext();
- if (mathContext == null) {
- RoundingMode roundingMode = properties.getRoundingMode();
- if (roundingMode == null) roundingMode = RoundingMode.HALF_EVEN;
- mathContext = MATH_CONTEXT_BY_ROUNDING_MODE_16_DIGITS[roundingMode.ordinal()];
- }
- return mathContext;
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number;
-
-import java.math.BigDecimal;
-import java.text.ParseException;
-import java.text.ParsePosition;
-import java.util.ArrayList;
-import java.util.List;
-
-import com.ibm.icu.impl.number.formatters.PaddingFormat.PadPosition;
-import com.ibm.icu.impl.number.formatters.RangeFormat;
-import com.ibm.icu.impl.number.modifiers.SimpleModifier;
-import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
-import com.ibm.icu.text.DecimalFormatSymbols;
-import com.ibm.icu.util.MeasureUnit;
-
-public class demo {
-
- public static void main(String[] args) throws ParseException {
- SimpleModifier.testFormatAsPrefixSuffix();
-
- System.out.println(new FormatQuantity1(3.14159));
- System.out.println(new FormatQuantity1(3.14159, true));
- System.out.println(new FormatQuantity2(3.14159));
-
- System.out.println(
- PatternString.propertiesToString(PatternString.parseToProperties("+**##,##,#00.05#%")));
-
- ParsePosition ppos = new ParsePosition(0);
- System.out.println(
- Parse.parse(
- "dd123",
- ppos,
- new Properties().setPositivePrefix("dd").setNegativePrefix("ddd"),
- DecimalFormatSymbols.getInstance()));
- System.out.println(ppos);
-
- List<Format> formats = new ArrayList<Format>();
-
- Properties properties = new Properties();
- Format ndf = Endpoint.fromBTA(properties);
- formats.add(ndf);
-
- properties =
- new Properties()
- .setMinimumSignificantDigits(3)
- .setMaximumSignificantDigits(3)
- .setCompactStyle(CompactStyle.LONG);
- Format cdf = Endpoint.fromBTA(properties);
- formats.add(cdf);
-
- properties =
- new Properties().setFormatWidth(10).setPadPosition(PadPosition.AFTER_PREFIX);
- Format pdf = Endpoint.fromBTA(properties);
- formats.add(pdf);
-
- properties =
- new Properties()
- .setMinimumExponentDigits(1)
- .setMaximumIntegerDigits(3)
- .setMaximumFractionDigits(1);
- Format exf = Endpoint.fromBTA(properties);
- formats.add(exf);
-
- properties = new Properties().setRoundingIncrement(new BigDecimal("0.5"));
- Format rif = Endpoint.fromBTA(properties);
- formats.add(rif);
-
- properties = new Properties().setMeasureUnit(MeasureUnit.HECTARE);
- Format muf = Endpoint.fromBTA(properties);
- formats.add(muf);
-
- properties =
- new Properties().setMeasureUnit(MeasureUnit.HECTARE).setCompactStyle(CompactStyle.LONG);
- Format cmf = Endpoint.fromBTA(properties);
- formats.add(cmf);
-
- properties = PatternString.parseToProperties("#,##0.00 \u00a4");
- Format ptf = Endpoint.fromBTA(properties);
- formats.add(ptf);
-
- RangeFormat rf = new RangeFormat(cdf, cdf, " to ");
- System.out.println(rf.format(new FormatQuantity2(1234), new FormatQuantity2(2345)));
-
- String[] cases = {
- "1.0",
- "2.01",
- "1234.56",
- "3000.0",
- // "512.0000000000017",
- // "4096.000000000001",
- // "4096.000000000004",
- // "4096.000000000005",
- // "4096.000000000006",
- // "4096.000000000007",
- "0.00026418",
- "0.01789261",
- "468160.0",
- "999000.0",
- "999900.0",
- "999990.0",
- "0.0",
- "12345678901.0",
- // "789000000000000000000000.0",
- // "789123123567853156372158.0",
- "-5193.48",
- };
-
- for (String str : cases) {
- System.out.println("----------");
- System.out.println(str);
- System.out.println(" NDF: " + ndf.format(new FormatQuantity2(Double.parseDouble(str))));
- System.out.println(" CDF: " + cdf.format(new FormatQuantity2(Double.parseDouble(str))));
- System.out.println(" PWD: " + pdf.format(new FormatQuantity2(Double.parseDouble(str))));
- System.out.println(" EXF: " + exf.format(new FormatQuantity2(Double.parseDouble(str))));
- System.out.println(" RIF: " + rif.format(new FormatQuantity2(Double.parseDouble(str))));
- System.out.println(" MUF: " + muf.format(new FormatQuantity2(Double.parseDouble(str))));
- System.out.println(" CMF: " + cmf.format(new FormatQuantity2(Double.parseDouble(str))));
- System.out.println(" PTF: " + ptf.format(new FormatQuantity2(Double.parseDouble(str))));
- }
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number.formatters;
-
-import java.math.BigDecimal;
-
-import com.ibm.icu.impl.number.Format.BeforeFormat;
-import com.ibm.icu.impl.number.FormatQuantity;
-import com.ibm.icu.impl.number.ModifierHolder;
-import com.ibm.icu.impl.number.Properties;
-
-public class BigDecimalMultiplier extends BeforeFormat {
- public static interface IProperties {
-
- static BigDecimal DEFAULT_MULTIPLIER = null;
-
- /** @see #setMultiplier */
- public BigDecimal getMultiplier();
-
- /**
- * Multiply all numbers by this amount before formatting.
- *
- * @param multiplier The amount to multiply by.
- * @return The property bag, for chaining.
- * @see MagnitudeMultiplier
- */
- public IProperties setMultiplier(BigDecimal multiplier);
- }
-
- public static boolean useMultiplier(IProperties properties) {
- return properties.getMultiplier() != IProperties.DEFAULT_MULTIPLIER;
- }
-
- private final BigDecimal multiplier;
-
- public static BigDecimalMultiplier getInstance(IProperties properties) {
- if (properties.getMultiplier() == null) {
- throw new IllegalArgumentException("The multiplier must be present for MultiplierFormat");
- }
- // TODO: Intelligently fall back to a MagnitudeMultiplier if the multiplier is a power of ten?
- return new BigDecimalMultiplier(properties);
- }
-
- private BigDecimalMultiplier(IProperties properties) {
- this.multiplier = properties.getMultiplier();
- }
-
- @Override
- public void before(FormatQuantity input, ModifierHolder mods) {
- input.multiplyBy(multiplier);
- }
-
- @Override
- public void export(Properties properties) {
- properties.setMultiplier(multiplier);
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number.formatters;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import com.ibm.icu.impl.ICUData;
-import com.ibm.icu.impl.ICUResourceBundle;
-import com.ibm.icu.impl.StandardPlural;
-import com.ibm.icu.impl.UResource;
-import com.ibm.icu.impl.number.Format;
-import com.ibm.icu.impl.number.FormatQuantity;
-import com.ibm.icu.impl.number.Modifier;
-import com.ibm.icu.impl.number.Modifier.PositiveNegativeModifier;
-import com.ibm.icu.impl.number.ModifierHolder;
-import com.ibm.icu.impl.number.PNAffixGenerator;
-import com.ibm.icu.impl.number.PatternString;
-import com.ibm.icu.impl.number.Properties;
-import com.ibm.icu.impl.number.Rounder;
-import com.ibm.icu.impl.number.modifiers.ConstantAffixModifier;
-import com.ibm.icu.impl.number.modifiers.PositiveNegativeAffixModifier;
-import com.ibm.icu.impl.number.rounders.SignificantDigitsRounder;
-import com.ibm.icu.impl.number.rounders.SignificantDigitsRounder.SignificantDigitsMode;
-import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
-import com.ibm.icu.text.DecimalFormatSymbols;
-import com.ibm.icu.text.NumberFormat;
-import com.ibm.icu.text.NumberingSystem;
-import com.ibm.icu.text.PluralRules;
-import com.ibm.icu.util.ULocale;
-import com.ibm.icu.util.UResourceBundle;
-
-public class CompactDecimalFormat extends Format.BeforeFormat {
- public static interface IProperties
- extends RoundingFormat.IProperties, CurrencyFormat.ICurrencyProperties {
-
- static CompactStyle DEFAULT_COMPACT_STYLE = null;
-
- /** @see #setCompactStyle */
- public CompactStyle getCompactStyle();
-
- /**
- * Use compact decimal formatting with the specified {@link CompactStyle}. CompactStyle.SHORT
- * produces output like "10K" in locale <em>en-US</em>, whereas CompactStyle.LONG produces
- * output like "10 thousand" in that locale.
- *
- * @param compactStyle The style of prefixes/suffixes to append.
- * @return The property bag, for chaining.
- */
- public IProperties setCompactStyle(CompactStyle compactStyle);
- }
-
- public static boolean useCompactDecimalFormat(IProperties properties) {
- return properties.getCompactStyle() != IProperties.DEFAULT_COMPACT_STYLE;
- }
-
- static final int MAX_DIGITS = 15;
-
- // Properties
- private final CompactDecimalData data;
- private final Rounder rounder;
- private final PositiveNegativeModifier defaultMod;
- private final CompactStyle style; // retained for exporting only
-
- public static CompactDecimalFormat getInstance(
- DecimalFormatSymbols symbols, IProperties properties) {
- return new CompactDecimalFormat(symbols, properties);
- }
-
- private static Rounder getRounder(IProperties properties) {
- // Use rounding settings if they were specified, or else use the default CDF rounder.
- Rounder rounder = RoundingFormat.getDefaultOrNull(properties);
- if (rounder == null) {
- rounder =
- SignificantDigitsRounder.getInstance(
- SignificantDigitsRounder.getThreadLocalProperties()
- .setMinimumSignificantDigits(1)
- .setMaximumSignificantDigits(2)
- .setSignificantDigitsMode(SignificantDigitsMode.OVERRIDE_MAXIMUM_FRACTION));
- }
- return rounder;
- }
-
- protected static final ThreadLocal<Map<CompactDecimalFingerprint, CompactDecimalData>>
- threadLocalDataCache =
- new ThreadLocal<Map<CompactDecimalFingerprint, CompactDecimalData>>() {
- @Override
- protected Map<CompactDecimalFingerprint, CompactDecimalData> initialValue() {
- return new HashMap<CompactDecimalFingerprint, CompactDecimalData>();
- }
- };
-
- private static CompactDecimalData getData(
- DecimalFormatSymbols symbols, CompactDecimalFingerprint fingerprint) {
- // See if we already have a data object based on the fingerprint
- CompactDecimalData data = threadLocalDataCache.get().get(fingerprint);
- if (data != null) return data;
-
- // Make data bundle object
- data = new CompactDecimalData();
- ULocale ulocale = symbols.getULocale();
- CompactDecimalDataSink sink = new CompactDecimalDataSink(data, symbols, fingerprint);
- String nsName = NumberingSystem.getInstance(ulocale).getName();
- ICUResourceBundle r =
- (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, ulocale);
- r.getAllItemsWithFallback("NumberElements/" + nsName, sink);
- if (data.isEmpty() && !nsName.equals("latn")) {
- r.getAllItemsWithFallback("NumberElements/latn", sink);
- }
- if (sink.exception != null) {
- throw sink.exception;
- }
- threadLocalDataCache.get().put(fingerprint, data);
- return data;
- }
-
- private static PositiveNegativeModifier getDefaultMod(
- DecimalFormatSymbols symbols, CompactDecimalFingerprint fingerprint) {
- ULocale uloc = symbols.getULocale();
- String pattern;
- if (fingerprint.compactType == CompactType.CURRENCY) {
- pattern = NumberFormat.getPattern(uloc, NumberFormat.CURRENCYSTYLE);
- } else {
- pattern = NumberFormat.getPattern(uloc, NumberFormat.NUMBERSTYLE);
- }
- // TODO: Clean this up; avoid the extra object creations.
- // TODO: Currency may also need to override grouping settings, not just affixes.
- Properties properties = PatternString.parseToProperties(pattern);
- PNAffixGenerator pnag = PNAffixGenerator.getThreadLocalInstance();
- PNAffixGenerator.Result result =
- pnag.getModifiers(symbols, fingerprint.currencySymbol, properties);
- return new PositiveNegativeAffixModifier(result.positive, result.negative);
- }
-
- private CompactDecimalFormat(DecimalFormatSymbols symbols, IProperties properties) {
- CompactDecimalFingerprint fingerprint = new CompactDecimalFingerprint(symbols, properties);
- this.rounder = getRounder(properties);
- this.data = getData(symbols, fingerprint);
- this.defaultMod = getDefaultMod(symbols, fingerprint);
- this.style = properties.getCompactStyle(); // for exporting only
- }
-
- @Override
- public void before(FormatQuantity input, ModifierHolder mods, PluralRules rules) {
- apply(input, mods, rules, rounder, data, defaultMod);
- }
-
- @Override
- protected void before(FormatQuantity input, ModifierHolder mods) {
- throw new UnsupportedOperationException();
- }
-
- public static void apply(
- FormatQuantity input,
- ModifierHolder mods,
- PluralRules rules,
- DecimalFormatSymbols symbols,
- IProperties properties) {
- CompactDecimalFingerprint fingerprint = new CompactDecimalFingerprint(symbols, properties);
- Rounder rounder = getRounder(properties);
- CompactDecimalData data = getData(symbols, fingerprint);
- PositiveNegativeModifier defaultMod = getDefaultMod(symbols, fingerprint);
- apply(input, mods, rules, rounder, data, defaultMod);
- }
-
- private static void apply(
- FormatQuantity input,
- ModifierHolder mods,
- PluralRules rules,
- Rounder rounder,
- CompactDecimalData data,
- PositiveNegativeModifier defaultMod) {
-
- // Treat zero as if it had magnitude 0
- int magnitude;
- if (input.isZero()) {
- magnitude = 0;
- rounder.apply(input);
- } else {
- int multiplier = rounder.chooseMultiplierAndApply(input, data);
- magnitude = input.getMagnitude() - multiplier;
- }
-
- StandardPlural plural = input.getStandardPlural(rules);
- boolean isNegative = input.isNegative();
- Modifier mod = data.getModifier(magnitude, plural, isNegative);
- if (mod == null) {
- // Use the default (non-compact) modifier.
- mod = defaultMod.getModifier(isNegative);
- }
- mods.add(mod);
- }
-
- @Override
- public void export(Properties properties) {
- properties.setCompactStyle(style);
- rounder.export(properties);
- }
-
- static class CompactDecimalData implements Rounder.MultiplierGenerator {
-
- // A dummy object used when a "0" compact decimal entry is encountered. This is necessary
- // in order to prevent falling back to root.
- private static final Modifier USE_FALLBACK = new ConstantAffixModifier();
-
- final Modifier[] mods;
- final byte[] multipliers;
- boolean isEmpty;
- int largestMagnitude;
-
- CompactDecimalData() {
- mods = new Modifier[(MAX_DIGITS + 1) * StandardPlural.COUNT * 2];
- multipliers = new byte[MAX_DIGITS + 1];
- isEmpty = true;
- largestMagnitude = -1;
- }
-
- boolean isEmpty() {
- return isEmpty;
- }
-
- @Override
- public int getMultiplier(int magnitude) {
- if (magnitude < 0) {
- return 0;
- }
- if (magnitude > largestMagnitude) {
- magnitude = largestMagnitude;
- }
- return multipliers[magnitude];
- }
-
- int setOrGetMultiplier(int magnitude, byte multiplier) {
- if (multipliers[magnitude] != 0) {
- return multipliers[magnitude];
- }
- multipliers[magnitude] = multiplier;
- isEmpty = false;
- if (magnitude > largestMagnitude) largestMagnitude = magnitude;
- return multiplier;
- }
-
- Modifier getModifier(int magnitude, StandardPlural plural, boolean isNegative) {
- if (magnitude < 0) {
- return null;
- }
- if (magnitude > largestMagnitude) {
- magnitude = largestMagnitude;
- }
- Modifier mod = mods[modIndex(magnitude, plural, isNegative)];
- if (mod == null && plural != StandardPlural.OTHER) {
- // Fall back to "other" plural variant
- mod = mods[modIndex(magnitude, StandardPlural.OTHER, isNegative)];
- }
- if (mod == USE_FALLBACK) {
- // Return null if USE_FALLBACK is present
- mod = null;
- }
- return mod;
- }
-
- public boolean has(int magnitude, StandardPlural plural) {
- // Return true if USE_FALLBACK is present
- return mods[modIndex(magnitude, plural, false)] != null;
- }
-
- void setModifiers(Modifier positive, Modifier negative, int magnitude, StandardPlural plural) {
- mods[modIndex(magnitude, plural, false)] = positive;
- mods[modIndex(magnitude, plural, true)] = negative;
- isEmpty = false;
- if (magnitude > largestMagnitude) largestMagnitude = magnitude;
- }
-
- void setNoFallback(int magnitude, StandardPlural plural) {
- setModifiers(USE_FALLBACK, USE_FALLBACK, magnitude, plural);
- }
-
- private static final int modIndex(int magnitude, StandardPlural plural, boolean isNegative) {
- return magnitude * StandardPlural.COUNT * 2 + plural.ordinal() * 2 + (isNegative ? 1 : 0);
- }
- }
-
- // Should this be public or internal?
- static enum CompactType {
- DECIMAL,
- CURRENCY
- }
-
- static class CompactDecimalFingerprint {
- // TODO: Add more stuff to the fingerprint, like the symbols used by PNAffixGenerator
- final CompactStyle compactStyle;
- final CompactType compactType;
- final ULocale uloc;
- final String currencySymbol;
-
- CompactDecimalFingerprint(DecimalFormatSymbols symbols, IProperties properties) {
- // CompactDecimalFormat does not need to worry about the same constraints as non-compact
- // currency formatting needs to consider, like the currency rounding mode and the currency
- // long names with plural forms.
- if (properties.getCurrency() != CurrencyFormat.ICurrencyProperties.DEFAULT_CURRENCY) {
- compactType = CompactType.CURRENCY;
- currencySymbol = CurrencyFormat.getCurrencySymbol(symbols, properties);
- } else {
- compactType = CompactType.DECIMAL;
- currencySymbol = symbols.getCurrencySymbol(); // fallback; should remain unused
- }
- compactStyle = properties.getCompactStyle();
- uloc = symbols.getULocale();
- }
-
- @Override
- public boolean equals(Object _other) {
- if (_other == null) return false;
- if (this == _other) return true;
- CompactDecimalFingerprint other = (CompactDecimalFingerprint) _other;
- if (compactStyle != other.compactStyle) return false;
- if (compactType != other.compactType) return false;
- if (currencySymbol != other.currencySymbol) {
- // String comparison with null handling
- if (currencySymbol == null || other.currencySymbol == null) return false;
- if (!currencySymbol.equals(other.currencySymbol)) return false;
- }
- if (!uloc.equals(other.uloc)) return false;
- return true;
- }
-
- @Override
- public int hashCode() {
- int hashCode = 0;
- if (compactStyle != null) hashCode ^= compactStyle.hashCode();
- if (compactType != null) hashCode ^= compactType.hashCode();
- if (uloc != null) hashCode ^= uloc.hashCode();
- if (currencySymbol != null) hashCode ^= currencySymbol.hashCode();
- return hashCode;
- }
- }
-
- private static final class CompactDecimalDataSink extends UResource.Sink {
-
- final CompactDecimalData data;
- final DecimalFormatSymbols symbols;
- final CompactStyle compactStyle;
- final CompactType compactType;
- final String currencySymbol;
- final PNAffixGenerator pnag;
- IllegalArgumentException exception;
-
- /*
- * NumberElements{ <-- top (numbering system table)
- * latn{ <-- patternsTable (one per numbering system)
- * patternsLong{ <-- formatsTable (one per pattern)
- * decimalFormat{ <-- powersOfTenTable (one per format)
- * 1000{ <-- pluralVariantsTable (one per power of ten)
- * one{"0 thousand"} <-- plural variant and template
- */
-
- public CompactDecimalDataSink(
- CompactDecimalData data,
- DecimalFormatSymbols symbols,
- CompactDecimalFingerprint fingerprint) {
- this.data = data;
- this.symbols = symbols;
- compactType = fingerprint.compactType;
- currencySymbol = fingerprint.currencySymbol;
- compactStyle = fingerprint.compactStyle;
- pnag = PNAffixGenerator.getThreadLocalInstance();
- }
-
- @Override
- public void put(UResource.Key key, UResource.Value value, boolean isRoot) {
- UResource.Table patternsTable = value.getTable();
- for (int i1 = 0; patternsTable.getKeyAndValue(i1, key, value); ++i1) {
- if (key.contentEquals("patternsShort") && compactStyle == CompactStyle.SHORT) {
- } else if (key.contentEquals("patternsLong") && compactStyle == CompactStyle.LONG) {
- } else {
- continue;
- }
-
- // traverse into the table of formats
- UResource.Table formatsTable = value.getTable();
- for (int i2 = 0; formatsTable.getKeyAndValue(i2, key, value); ++i2) {
- if (key.contentEquals("decimalFormat") && compactType == CompactType.DECIMAL) {
- } else if (key.contentEquals("currencyFormat") && compactType == CompactType.CURRENCY) {
- } else {
- continue;
- }
-
- // traverse into the table of powers of ten
- UResource.Table powersOfTenTable = value.getTable();
- for (int i3 = 0; powersOfTenTable.getKeyAndValue(i3, key, value); ++i3) {
- try {
-
- // Assumes that the keys are always of the form "10000" where the magnitude is the
- // length of the key minus one
- byte magnitude = (byte) (key.length() - 1);
-
- // Silently ignore divisors that are too big.
- if (magnitude >= MAX_DIGITS) continue;
-
- // Iterate over the plural variants ("one", "other", etc)
- UResource.Table pluralVariantsTable = value.getTable();
- for (int i4 = 0; pluralVariantsTable.getKeyAndValue(i4, key, value); ++i4) {
-
- // Skip this magnitude/plural if we already have it from a child locale.
- StandardPlural plural = StandardPlural.fromString(key.toString());
- if (data.has(magnitude, plural)) {
- continue;
- }
-
- // The value "0" means that we need to use the default pattern and not fall back
- // to parent locales. Example locale where this is relevant: 'it'.
- String patternString = value.toString();
- if (patternString.equals("0")) {
- data.setNoFallback(magnitude, plural);
- continue;
- }
-
- // The magnitude multiplier is the difference between the magnitude and the number
- // of zeros in the pattern, getMinimumIntegerDigits.
- Properties properties = PatternString.parseToProperties(patternString);
- byte _multiplier = (byte) -(magnitude - properties.getMinimumIntegerDigits() + 1);
- if (_multiplier != data.setOrGetMultiplier(magnitude, _multiplier)) {
- throw new IllegalArgumentException(
- String.format(
- "Different number of zeros for same power of ten in compact decimal format data for locale '%s', style '%s', type '%s'",
- symbols.getULocale().toString(),
- compactStyle.toString(),
- compactType.toString()));
- }
-
- PNAffixGenerator.Result result =
- pnag.getModifiers(symbols, currencySymbol, properties);
- data.setModifiers(result.positive, result.negative, magnitude, plural);
- }
-
- } catch (IllegalArgumentException e) {
- exception = e;
- continue;
- }
- }
-
- // We want only one table of compact decimal formats, so if we get here, stop consuming.
- // The data.isEmpty() check will prevent further bundles from being traversed.
- return;
- }
- }
- }
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number.formatters;
-
-import java.math.BigDecimal;
-
-import com.ibm.icu.impl.StandardPlural;
-import com.ibm.icu.impl.number.AffixPatternUtils;
-import com.ibm.icu.impl.number.PNAffixGenerator;
-import com.ibm.icu.impl.number.PatternString;
-import com.ibm.icu.impl.number.Properties;
-import com.ibm.icu.impl.number.Rounder;
-import com.ibm.icu.impl.number.modifiers.GeneralPluralModifier;
-import com.ibm.icu.impl.number.rounders.IncrementRounder;
-import com.ibm.icu.impl.number.rounders.MagnitudeRounder;
-import com.ibm.icu.text.CurrencyPluralInfo;
-import com.ibm.icu.text.DecimalFormatSymbols;
-import com.ibm.icu.util.Currency;
-import com.ibm.icu.util.Currency.CurrencyUsage;
-
-public class CurrencyFormat {
-
- public enum CurrencyStyle {
- SYMBOL,
- ISO_CODE;
- }
-
- public static interface ICurrencyProperties {
- static Currency DEFAULT_CURRENCY = null;
-
- /** @see #setCurrency */
- public Currency getCurrency();
-
- /**
- * Use the specified currency to substitute currency placeholders ('¤') in the pattern string.
- *
- * @param currency The currency.
- * @return The property bag, for chaining.
- */
- public IProperties setCurrency(Currency currency);
-
- static CurrencyStyle DEFAULT_CURRENCY_STYLE = null;
-
- /** @see #setCurrencyStyle */
- public CurrencyStyle getCurrencyStyle();
-
- /**
- * Use the specified {@link CurrencyStyle} to replace currency placeholders ('¤').
- * CurrencyStyle.SYMBOL will use the short currency symbol, like "$" or "€", whereas
- * CurrencyStyle.ISO_CODE will use the ISO 4217 currency code, like "USD" or "EUR".
- *
- * <p>For long currency names, use {@link MeasureFormat.IProperties#setMeasureUnit}.
- *
- * @param currencyStyle The currency style. Defaults to CurrencyStyle.SYMBOL.
- * @return The property bag, for chaining.
- */
- public IProperties setCurrencyStyle(CurrencyStyle currencyStyle);
-
- /**
- * An old enum that specifies how currencies should be rounded. It contains a subset of the
- * functionality supported by RoundingInterval.
- */
- static Currency.CurrencyUsage DEFAULT_CURRENCY_USAGE = null;
-
- /** @see #setCurrencyUsage */
- public Currency.CurrencyUsage getCurrencyUsage();
-
- /**
- * Use the specified {@link CurrencyUsage} instance, which provides default rounding rules for
- * the currency in two styles, CurrencyUsage.CASH and CurrencyUsage.STANDARD.
- *
- * <p>The CurrencyUsage specified here will not be used unless there is a currency placeholder
- * in the pattern.
- *
- * @param currencyUsage The currency usage. Defaults to CurrencyUsage.STANDARD.
- * @return The property bag, for chaining.
- */
- public IProperties setCurrencyUsage(Currency.CurrencyUsage currencyUsage);
-
- static CurrencyPluralInfo DEFAULT_CURRENCY_PLURAL_INFO = null;
-
- /** @see #setCurrencyPluralInfo */
- @Deprecated
- public CurrencyPluralInfo getCurrencyPluralInfo();
-
- /**
- * Use the specified {@link CurrencyPluralInfo} instance when formatting currency long names.
- *
- * @param currencyPluralInfo The currency plural info object.
- * @return The property bag, for chaining.
- * @deprecated Use {@link MeasureFormat.IProperties#setMeasureUnit} with a Currency instead.
- */
- @Deprecated
- public IProperties setCurrencyPluralInfo(CurrencyPluralInfo currencyPluralInfo);
-
- public IProperties clone();
- }
-
- public static interface IProperties
- extends ICurrencyProperties,
- RoundingFormat.IProperties,
- PositiveNegativeAffixFormat.IProperties {}
-
- /**
- * Returns true if the currency is set in The property bag or if currency symbols are present in
- * the prefix/suffix pattern.
- */
- public static boolean useCurrency(IProperties properties) {
- return ((properties.getCurrency() != null)
- || properties.getCurrencyPluralInfo() != null
- || AffixPatternUtils.hasCurrencySymbols(properties.getPositivePrefixPattern())
- || AffixPatternUtils.hasCurrencySymbols(properties.getPositiveSuffixPattern())
- || AffixPatternUtils.hasCurrencySymbols(properties.getNegativePrefixPattern())
- || AffixPatternUtils.hasCurrencySymbols(properties.getNegativeSuffixPattern()));
- }
-
- /**
- * Returns the effective currency symbol based on the input. If {@link
- * ICurrencyProperties#setCurrencyStyle} was set to {@link CurrencyStyle#ISO_CODE}, the ISO Code
- * will be returned; otherwise, the currency symbol, like "$", will be returned.
- *
- * @param symbols The current {@link DecimalFormatSymbols} instance
- * @param properties The current property bag
- * @return The currency symbol string, e.g., to substitute '¤' in a decimal pattern string.
- */
- public static String getCurrencySymbol(
- DecimalFormatSymbols symbols, ICurrencyProperties properties) {
- // If the user asked for ISO Code, return the ISO Code instead of the symbol
- CurrencyStyle style = properties.getCurrencyStyle();
- if (style == CurrencyStyle.ISO_CODE) {
- return getCurrencyIsoCode(symbols, properties);
- }
-
- // Get the currency symbol
- Currency currency = properties.getCurrency();
- if (currency == null) {
- return symbols.getCurrencySymbol();
- } else if (currency.equals(symbols.getCurrency())) {
- // The user may have set a custom currency symbol in DecimalFormatSymbols.
- return symbols.getCurrencySymbol();
- } else {
- // Use the canonical symbol.
- return currency.getName(symbols.getULocale(), Currency.SYMBOL_NAME, null);
- }
- }
-
- /**
- * Returns the currency ISO code based on the input, like "USD".
- *
- * @param symbols The current {@link DecimalFormatSymbols} instance
- * @param properties The current property bag
- * @return The currency ISO code string, e.g., to substitute '¤¤' in a decimal pattern string.
- */
- public static String getCurrencyIsoCode(
- DecimalFormatSymbols symbols, ICurrencyProperties properties) {
- Currency currency = properties.getCurrency();
- if (currency == null) {
- // If a currency object was not provided, use the string from symbols
- // Note: symbols.getCurrency().getCurrencyCode() won't work here because
- // DecimalFormatSymbols#setInternationalCurrencySymbol() does not update the
- // immutable internal currency instance.
- return symbols.getInternationalCurrencySymbol();
- } else if (currency.equals(symbols.getCurrency())) {
- // The user may have set a custom currency symbol in DecimalFormatSymbols.
- return symbols.getInternationalCurrencySymbol();
- } else {
- // Use the canonical currency code.
- return currency.getCurrencyCode();
- }
- }
-
- /**
- * Returns the currency long name on the input, like "US dollars".
- *
- * @param symbols The current {@link DecimalFormatSymbols} instance
- * @param properties The current property bag
- * @param plural The plural form
- * @return The currency long name string, e.g., to substitute '¤¤¤' in a decimal pattern string.
- */
- public static String getCurrencyLongName(
- DecimalFormatSymbols symbols, ICurrencyProperties properties, StandardPlural plural) {
- // Attempt to get a currency object first from properties then from symbols
- Currency currency = properties.getCurrency();
- if (currency == null) {
- currency = symbols.getCurrency();
- }
-
- // If no currency object is available, fall back to the currency symbol
- if (currency == null) {
- return getCurrencySymbol(symbols, properties);
- }
-
- // Get the long name
- return currency.getName(
- symbols.getULocale(), Currency.PLURAL_LONG_NAME, plural.getKeyword(), null);
- }
-
- public static GeneralPluralModifier getCurrencyModifier(
- DecimalFormatSymbols symbols, IProperties properties) {
-
- PNAffixGenerator pnag = PNAffixGenerator.getThreadLocalInstance();
- String sym = getCurrencySymbol(symbols, properties);
- String iso = getCurrencyIsoCode(symbols, properties);
-
- // Previously, the user was also able to specify '¤¤' and '¤¤¤' directly into the prefix or
- // suffix, which is how the user specified whether they wanted the ISO code or long name.
- // For backwards compatibility support, that feature is implemented here.
-
- CurrencyPluralInfo info = properties.getCurrencyPluralInfo();
- GeneralPluralModifier mod = new GeneralPluralModifier();
- Properties temp = new Properties();
- for (StandardPlural plural : StandardPlural.VALUES) {
- String longName = getCurrencyLongName(symbols, properties, plural);
-
- PNAffixGenerator.Result result;
- if (info == null) {
- // CurrencyPluralInfo is not available.
- result = pnag.getModifiers(symbols, sym, iso, longName, properties);
- } else {
- // CurrencyPluralInfo is available. Use it to generate affixes for long name support.
- String pluralPattern = info.getCurrencyPluralPattern(plural.getKeyword());
- PatternString.parseToExistingProperties(pluralPattern, temp, true);
- result = pnag.getModifiers(symbols, sym, iso, longName, temp);
- }
- mod.put(plural, result.positive, result.negative);
- }
- return mod;
- }
-
- private static final Currency DEFAULT_CURRENCY = Currency.getInstance("XXX");
-
- public static void populateCurrencyRounderProperties(
- Properties destination, DecimalFormatSymbols symbols, IProperties properties) {
-
- Currency currency = properties.getCurrency();
- if (currency == null) {
- // Fall back to the DecimalFormatSymbols currency instance.
- currency = symbols.getCurrency();
- }
- if (currency == null) {
- // There is a currency symbol in the pattern, but we have no currency available to use.
- // Use the default currency instead so that we can still apply currency usage rules.
- currency = DEFAULT_CURRENCY;
- }
-
- Currency.CurrencyUsage currencyUsage = properties.getCurrencyUsage();
- if (currencyUsage == null) {
- currencyUsage = CurrencyUsage.STANDARD;
- }
-
- double incrementDouble = currency.getRoundingIncrement(currencyUsage);
- int fractionDigits = currency.getDefaultFractionDigits(currencyUsage);
-
- destination.setRoundingMode(properties.getRoundingMode());
- destination.setMinimumIntegerDigits(properties.getMinimumIntegerDigits());
- destination.setMaximumIntegerDigits(properties.getMaximumIntegerDigits());
-
- int _minFrac = properties.getMinimumFractionDigits();
- int _maxFrac = properties.getMaximumFractionDigits();
- if (_minFrac >= 0 || _maxFrac >= 0) {
- // User override
- destination.setMinimumFractionDigits(_minFrac);
- destination.setMaximumFractionDigits(_maxFrac);
- } else {
- destination.setMinimumFractionDigits(fractionDigits);
- destination.setMaximumFractionDigits(fractionDigits);
- }
-
- if (incrementDouble > 0.0) {
- BigDecimal incrementBigDecimal;
- BigDecimal _roundingIncrement = properties.getRoundingIncrement();
- if (_roundingIncrement != null) {
- incrementBigDecimal = _roundingIncrement;
- } else {
- incrementBigDecimal = BigDecimal.valueOf(incrementDouble);
- }
- destination.setRoundingIncrement(incrementBigDecimal);
- } else {
- }
- }
-
- private static final ThreadLocal<Properties> threadLocalProperties =
- new ThreadLocal<Properties>() {
- @Override
- protected Properties initialValue() {
- return new Properties();
- }
- };
-
- public static Rounder getCurrencyRounder(DecimalFormatSymbols symbols, IProperties properties) {
- Properties cprops = threadLocalProperties.get().clear();
- populateCurrencyRounderProperties(cprops, symbols, properties);
- if (cprops.getRoundingIncrement() != null) {
- return IncrementRounder.getInstance(cprops);
- } else {
- return MagnitudeRounder.getInstance(cprops);
- }
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number.formatters;
-
-import com.ibm.icu.impl.number.Format;
-import com.ibm.icu.impl.number.Format.BeforeFormat;
-import com.ibm.icu.impl.number.FormatQuantity;
-import com.ibm.icu.impl.number.ModifierHolder;
-import com.ibm.icu.impl.number.Properties;
-
-public class MagnitudeMultiplier extends Format.BeforeFormat {
- private static final MagnitudeMultiplier DEFAULT = new MagnitudeMultiplier(0);
-
- public static interface IProperties {
-
- static int DEFAULT_MAGNITUDE_MULTIPLIER = 0;
-
- /** @see #setMagnitudeMultiplier */
- public int getMagnitudeMultiplier();
-
- /**
- * Multiply all numbers by this power of ten before formatting. Negative multipliers reduce the
- * magnitude and make numbers smaller (closer to zero).
- *
- * @param magnitudeMultiplier The number of powers of ten to scale.
- * @return The property bag, for chaining.
- * @see BigDecimalMultiplier
- */
- public IProperties setMagnitudeMultiplier(int magnitudeMultiplier);
- }
-
- public static boolean useMagnitudeMultiplier(IProperties properties) {
- return properties.getMagnitudeMultiplier() != IProperties.DEFAULT_MAGNITUDE_MULTIPLIER;
- }
-
- // Properties
- final int delta;
-
- public static BeforeFormat getInstance(Properties properties) {
- if (properties.getMagnitudeMultiplier() == 0) {
- return DEFAULT;
- }
- return new MagnitudeMultiplier(properties.getMagnitudeMultiplier());
- }
-
- private MagnitudeMultiplier(int delta) {
- this.delta = delta;
- }
-
- @Override
- public void before(FormatQuantity input, ModifierHolder mods) {
- input.adjustMagnitude(delta);
- }
-
- @Override
- public void export(Properties properties) {
- properties.setMagnitudeMultiplier(delta);
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number.formatters;
-
-import com.ibm.icu.impl.StandardPlural;
-import com.ibm.icu.impl.number.modifiers.GeneralPluralModifier;
-import com.ibm.icu.impl.number.modifiers.SimpleModifier;
-import com.ibm.icu.text.DecimalFormatSymbols;
-import com.ibm.icu.text.MeasureFormat.FormatWidth;
-import com.ibm.icu.util.MeasureUnit;
-import com.ibm.icu.util.ULocale;
-
-public class MeasureFormat {
-
- public static interface IProperties {
-
- static MeasureUnit DEFAULT_MEASURE_UNIT = null;
-
- /** @see #setMeasureUnit */
- public MeasureUnit getMeasureUnit();
-
- /**
- * Apply prefixes and suffixes for the specified {@link MeasureUnit} to the formatted number.
- *
- * @param measureUnit The measure unit.
- * @return The property bag, for chaining.
- */
- public IProperties setMeasureUnit(MeasureUnit measureUnit);
-
- static FormatWidth DEFAULT_MEASURE_FORMAT_WIDTH = null;
-
- /** @see #setMeasureFormatWidth */
- public FormatWidth getMeasureFormatWidth();
-
- /**
- * Use the specified {@link FormatWidth} when choosing the style of measure unit prefix/suffix.
- *
- * <p>Must be used in conjunction with {@link #setMeasureUnit}.
- *
- * @param measureFormatWidth The width style. Defaults to FormatWidth.WIDE.
- * @return The property bag, for chaining.
- */
- public IProperties setMeasureFormatWidth(FormatWidth measureFormatWidth);
- }
-
- public static boolean useMeasureFormat(IProperties properties) {
- return properties.getMeasureUnit() != IProperties.DEFAULT_MEASURE_UNIT;
- }
-
- public static GeneralPluralModifier getInstance(DecimalFormatSymbols symbols, IProperties properties) {
- ULocale uloc = symbols.getULocale();
- MeasureUnit unit = properties.getMeasureUnit();
- FormatWidth width = properties.getMeasureFormatWidth();
-
- if (unit == null) {
- throw new IllegalArgumentException("A measure unit is required for MeasureFormat");
- }
- if (width == null) {
- width = FormatWidth.WIDE;
- }
-
- // Temporarily, create a MeasureFormat instance for its data loading capability
- // TODO: Move data loading directly into this class file
- com.ibm.icu.text.MeasureFormat mf = com.ibm.icu.text.MeasureFormat.getInstance(uloc, width);
- GeneralPluralModifier mod = new GeneralPluralModifier();
- for (StandardPlural plural : StandardPlural.VALUES) {
- String formatString = null;
- mf.getPluralFormatter(unit, width, plural.ordinal());
- mod.put(plural, new SimpleModifier(formatString, null, false));
- }
- return mod;
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number.formatters;
-
-import com.ibm.icu.impl.number.Format.AfterFormat;
-import com.ibm.icu.impl.number.ModifierHolder;
-import com.ibm.icu.impl.number.NumberStringBuilder;
-import com.ibm.icu.impl.number.Properties;
-
-public class PaddingFormat implements AfterFormat {
- public enum PadPosition {
- BEFORE_PREFIX,
- AFTER_PREFIX,
- BEFORE_SUFFIX,
- AFTER_SUFFIX;
-
- public static PadPosition fromOld(int old) {
- switch (old) {
- case com.ibm.icu.text.DecimalFormat.PAD_BEFORE_PREFIX:
- return PadPosition.BEFORE_PREFIX;
- case com.ibm.icu.text.DecimalFormat.PAD_AFTER_PREFIX:
- return PadPosition.AFTER_PREFIX;
- case com.ibm.icu.text.DecimalFormat.PAD_BEFORE_SUFFIX:
- return PadPosition.BEFORE_SUFFIX;
- case com.ibm.icu.text.DecimalFormat.PAD_AFTER_SUFFIX:
- return PadPosition.AFTER_SUFFIX;
- default:
- throw new IllegalArgumentException("Don't know how to map " + old);
- }
- }
-
- public int toOld() {
- switch (this) {
- case BEFORE_PREFIX:
- return com.ibm.icu.text.DecimalFormat.PAD_BEFORE_PREFIX;
- case AFTER_PREFIX:
- return com.ibm.icu.text.DecimalFormat.PAD_AFTER_PREFIX;
- case BEFORE_SUFFIX:
- return com.ibm.icu.text.DecimalFormat.PAD_BEFORE_SUFFIX;
- case AFTER_SUFFIX:
- return com.ibm.icu.text.DecimalFormat.PAD_AFTER_SUFFIX;
- default:
- return -1; // silence compiler errors
- }
- }
- }
-
- public static interface IProperties {
-
- static int DEFAULT_FORMAT_WIDTH = 0;
-
- /** @see #setFormatWidth */
- public int getFormatWidth();
-
- /**
- * Sets the minimum width of the string output by the formatting pipeline. For example, if
- * padding is enabled and paddingWidth is set to 6, formatting the number "3.14159" with the
- * pattern "0.00" will result in "··3.14" if '·' is your padding string.
- *
- * <p>If the number is longer than your padding width, the number will display as if no padding
- * width had been specified, which may result in strings longer than the padding width.
- *
- * <p>Width is counted in UTF-16 code units.
- *
- * @param formatWidth The output width.
- * @return The property bag, for chaining.
- * @see #setPadPosition
- * @see #setPadString
- */
- public IProperties setFormatWidth(int formatWidth);
-
- static String DEFAULT_PAD_STRING = null;
-
- /** @see #setPadString */
- public String getPadString();
-
- /**
- * Sets the string used for padding. The string should contain a single character or grapheme
- * cluster.
- *
- * <p>Must be used in conjunction with {@link #setFormatWidth}.
- *
- * @param paddingString The padding string. Defaults to an ASCII space (U+0020).
- * @return The property bag, for chaining.
- * @see #setFormatWidth
- */
- public IProperties setPadString(String paddingString);
-
- static PadPosition DEFAULT_PAD_POSITION = null;
-
- /** @see #setPadPosition */
- public PadPosition getPadPosition();
-
- /**
- * Sets the location where the padding string is to be inserted to maintain the padding width:
- * one of BEFORE_PREFIX, AFTER_PREFIX, BEFORE_SUFFIX, or AFTER_SUFFIX.
- *
- * <p>Must be used in conjunction with {@link #setFormatWidth}.
- *
- * @param padPosition The output width.
- * @return The property bag, for chaining.
- * @see #setFormatWidth
- */
- public IProperties setPadPosition(PadPosition padPosition);
- }
-
- public static final String FALLBACK_PADDING_STRING = "\u0020"; // i.e. a space
-
- public static boolean usePadding(IProperties properties) {
- return properties.getFormatWidth() != IProperties.DEFAULT_FORMAT_WIDTH;
- }
-
- public static AfterFormat getInstance(IProperties properties) {
- return new PaddingFormat(
- properties.getFormatWidth(),
- properties.getPadString(),
- properties.getPadPosition());
- }
-
- // Properties
- private final int paddingWidth;
- private final String paddingString;
- private final PadPosition paddingLocation;
-
- private PaddingFormat(
- int paddingWidth, String paddingString, PadPosition paddingLocation) {
- this.paddingWidth = paddingWidth > 0 ? paddingWidth : 10; // TODO: Is this a sensible default?
- this.paddingString = paddingString != null ? paddingString : FALLBACK_PADDING_STRING;
- this.paddingLocation =
- paddingLocation != null ? paddingLocation : PadPosition.BEFORE_PREFIX;
- }
-
- @Override
- public int after(ModifierHolder mods, NumberStringBuilder string, int leftIndex, int rightIndex) {
-
- // TODO: Count code points instead of code units?
- int requiredPadding = paddingWidth - (rightIndex - leftIndex) - mods.totalLength();
-
- if (requiredPadding <= 0) {
- // Skip padding, but still apply modifiers to be consistent
- return mods.applyAll(string, leftIndex, rightIndex);
- }
-
- int length = 0;
- if (paddingLocation == PadPosition.AFTER_PREFIX) {
- length += addPadding(requiredPadding, string, leftIndex);
- } else if (paddingLocation == PadPosition.BEFORE_SUFFIX) {
- length += addPadding(requiredPadding, string, rightIndex);
- }
- length += mods.applyAll(string, leftIndex, rightIndex + length);
- if (paddingLocation == PadPosition.BEFORE_PREFIX) {
- length += addPadding(requiredPadding, string, leftIndex);
- } else if (paddingLocation == PadPosition.AFTER_SUFFIX) {
- length += addPadding(requiredPadding, string, rightIndex + length);
- }
-
- return length;
- }
-
- private int addPadding(int requiredPadding, NumberStringBuilder string, int index) {
- for (int i = 0; i < requiredPadding; i++) {
- string.insert(index, paddingString, null);
- }
- return paddingString.length() * requiredPadding;
- }
-
- @Override
- public void export(Properties properties) {
- properties.setFormatWidth(paddingWidth);
- properties.setPadString(paddingString);
- properties.setPadPosition(paddingLocation);
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number.formatters;
-
-import com.ibm.icu.impl.number.Format;
-import com.ibm.icu.impl.number.FormatQuantity;
-import com.ibm.icu.impl.number.NumberStringBuilder;
-import com.ibm.icu.impl.number.Properties;
-import com.ibm.icu.text.DecimalFormatSymbols;
-import com.ibm.icu.text.NumberFormat;
-import com.ibm.icu.text.NumberFormat.Field;
-
-public class PositiveDecimalFormat implements Format.TargetFormat {
-
- public static interface IProperties extends CurrencyFormat.IProperties {
-
- static int DEFAULT_GROUPING_SIZE = -1;
-
- /** @see #setGroupingSize */
- public int getGroupingSize();
-
- /**
- * Sets the number of digits between grouping separators. For example, the <em>en-US</em> locale
- * uses a grouping size of 3, so the number 1234567 would be formatted as "1,234,567". For
- * locales whose grouping sizes vary with magnitude, see {@link #setSecondaryGroupingSize(int)}.
- *
- * @param groupingSize The primary grouping size.
- * @return The property bag, for chaining.
- */
- public IProperties setGroupingSize(int groupingSize);
-
- static int DEFAULT_SECONDARY_GROUPING_SIZE = -1;
-
- /** @see #setSecondaryGroupingSize */
- public int getSecondaryGroupingSize();
-
- /**
- * Sets the number of digits between grouping separators higher than the least-significant
- * grouping separator. For example, the locale <em>hi</em> uses a primary grouping size of 3 and
- * a secondary grouping size of 2, so the number 1234567 would be formatted as "12,34,567".
- *
- * <p>The two levels of grouping separators can be specified in the pattern string. For example,
- * the <em>hi</em> locale's default decimal format pattern is "#,##,##0.###".
- *
- * @param secondaryGroupingSize The secondary grouping size.
- * @return The property bag, for chaining.
- */
- public IProperties setSecondaryGroupingSize(int secondaryGroupingSize);
-
- static boolean DEFAULT_DECIMAL_SEPARATOR_ALWAYS_SHOWN = false;
-
- /** @see #setDecimalSeparatorAlwaysShown */
- public boolean getDecimalSeparatorAlwaysShown();
-
- /**
- * Sets whether to always show the decimal point, even if the number doesn't require one. For
- * example, if always show decimal is true, the number 123 would be formatted as "123." in
- * locale <em>en-US</em>.
- *
- * @param decimalSeparatorAlwaysShown Whether to show the decimal point when it is optional.
- * @return The property bag, for chaining.
- */
- public IProperties setDecimalSeparatorAlwaysShown(boolean decimalSeparatorAlwaysShown);
-
- static int DEFAULT_MINIMUM_GROUPING_DIGITS = 1;
-
- /** @see #setMinimumGroupingDigits */
- public int getMinimumGroupingDigits();
-
- /**
- * Sets the minimum number of digits required to be beyond the first grouping separator in order
- * to enable grouping. For example, if the minimum grouping digits is 2, then 1234 would be
- * formatted as "1234" but 12345 would be formatted as "12,345" in <em>en-US</em>. Note that
- * 1234567 would still be formatted as "1,234,567", not "1234,567".
- *
- * @param minimumGroupingDigits How many digits must appear before a grouping separator before
- * enabling grouping.
- * @return The property bag, for chaining.
- */
- public IProperties setMinimumGroupingDigits(int minimumGroupingDigits);
- }
-
- public static boolean useGrouping(IProperties properties) {
- return properties.getGroupingSize() != IProperties.DEFAULT_GROUPING_SIZE
- || properties.getSecondaryGroupingSize() != IProperties.DEFAULT_SECONDARY_GROUPING_SIZE;
- }
-
- public static boolean allowsDecimalPoint(IProperties properties) {
- return properties.getDecimalSeparatorAlwaysShown() || properties.getMaximumFractionDigits() != 0;
- }
-
- // Properties
- private final boolean alwaysShowDecimal;
- private final int groupingSize;
- private final int secondaryGroupingSize;
- private final int minimumGroupingDigits;
-
- // Symbols
- private final String infinityString;
- private final String nanString;
- private final String groupingSeparator;
- private final String decimalSeparator;
- private final String[] digitStrings;
- private final int codePointZero;
-
- public PositiveDecimalFormat(DecimalFormatSymbols symbols, IProperties properties) {
- groupingSize =
- (properties.getGroupingSize() < 0)
- ? properties.getSecondaryGroupingSize()
- : properties.getGroupingSize();
- secondaryGroupingSize =
- (properties.getSecondaryGroupingSize() < 0)
- ? properties.getGroupingSize()
- : properties.getSecondaryGroupingSize();
-
- minimumGroupingDigits = properties.getMinimumGroupingDigits();
- alwaysShowDecimal = properties.getDecimalSeparatorAlwaysShown();
- infinityString = symbols.getInfinity();
- nanString = symbols.getNaN();
-
- if (CurrencyFormat.useCurrency(properties)) {
- groupingSeparator = symbols.getMonetaryGroupingSeparatorString();
- decimalSeparator = symbols.getMonetaryDecimalSeparatorString();
- } else {
- groupingSeparator = symbols.getGroupingSeparatorString();
- decimalSeparator = symbols.getDecimalSeparatorString();
- }
-
- // Check to see if we can use code points instead of strings (~15% format performance boost)
- int _codePointZero = -1;
- String[] _digitStrings = symbols.getDigitStringsLocal();
- for (int i = 0; i < _digitStrings.length; i++) {
- int cp = Character.codePointAt(_digitStrings[i], 0);
- int cc = Character.charCount(cp);
- if (cc != _digitStrings[i].length()) {
- _codePointZero = -1;
- break;
- } else if (i == 0) {
- _codePointZero = cp;
- } else if (cp != _codePointZero + i) {
- _codePointZero = -1;
- break;
- }
- }
- if (_codePointZero != -1) {
- digitStrings = null;
- codePointZero = _codePointZero;
- } else {
- digitStrings = symbols.getDigitStrings(); // makes a copy
- codePointZero = -1;
- }
- }
-
- @Override
- public int target(FormatQuantity input, NumberStringBuilder string, int startIndex) {
- int length = 0;
-
- if (input.isInfinite()) {
- length += string.insert(startIndex, infinityString, NumberFormat.Field.INTEGER);
-
- } else if (input.isNaN()) {
- length += string.insert(startIndex, nanString, NumberFormat.Field.INTEGER);
-
- } else {
- // Add the integer digits
- length += addIntegerDigits(input, string, startIndex);
-
- // Add the decimal point
- if (input.getLowerDisplayMagnitude() < 0 || alwaysShowDecimal) {
- length += string.insert(startIndex + length, decimalSeparator, NumberFormat.Field.DECIMAL_SEPARATOR);
- }
-
- // Add the fraction digits
- length += addFractionDigits(input, string, startIndex + length);
- }
-
- return length;
- }
-
- private int addIntegerDigits(FormatQuantity input, NumberStringBuilder string, int startIndex) {
- int length = 0;
- int integerCount = input.getUpperDisplayMagnitude() + 1;
- for (int i = 0; i < integerCount; i++) {
- // Add grouping separator
- if (groupingSize > 0 && i == groupingSize && integerCount - i >= minimumGroupingDigits) {
- length += string.insert(startIndex, groupingSeparator, NumberFormat.Field.GROUPING_SEPARATOR);
- } else if (secondaryGroupingSize > 0
- && i > groupingSize
- && (i - groupingSize) % secondaryGroupingSize == 0) {
- length += string.insert(startIndex, groupingSeparator, NumberFormat.Field.GROUPING_SEPARATOR);
- }
-
- // Get and append the next digit value
- byte nextDigit = input.getDigit(i);
- length += addDigit(nextDigit, string, startIndex, NumberFormat.Field.INTEGER);
- }
-
- return length;
- }
-
- private int addFractionDigits(FormatQuantity input, NumberStringBuilder string, int index) {
- int length = 0;
- int fractionCount = -input.getLowerDisplayMagnitude();
- for (int i = 0; i < fractionCount; i++) {
- // Get and append the next digit value
- byte nextDigit = input.getDigit(-i - 1);
- length += addDigit(nextDigit, string, index + length, NumberFormat.Field.FRACTION);
- }
- return length;
- }
-
- private int addDigit(byte digit, NumberStringBuilder outputString, int index, Field field) {
- if (codePointZero != -1) {
- return outputString.insertCodePoint(index, codePointZero + digit, field);
- } else {
- return outputString.insert(index, digitStrings[digit], field);
- }
- }
-
- @Override
- public void export(Properties properties) {
- properties.setDecimalSeparatorAlwaysShown(alwaysShowDecimal);
- properties.setGroupingSize(groupingSize);
- properties.setSecondaryGroupingSize(secondaryGroupingSize);
- properties.setMinimumGroupingDigits(minimumGroupingDigits);
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number.formatters;
-
-import com.ibm.icu.impl.number.FormatQuantity;
-import com.ibm.icu.impl.number.ModifierHolder;
-import com.ibm.icu.impl.number.PNAffixGenerator;
-import com.ibm.icu.impl.number.modifiers.PositiveNegativeAffixModifier;
-import com.ibm.icu.text.DecimalFormatSymbols;
-
-/**
- * The implementation of this class is a thin wrapper around {@link PNAffixGenerator}, a utility
- * used by this and other classes, including {@link CompactDecimalFormat} and {@link Parse}, to
- * efficiently convert from the abstract properties in the property bag to actual prefix and suffix
- * strings.
- */
-
-/**
- * This class is responsible for adding the positive/negative prefixes and suffixes from the decimal
- * format pattern. Properties are set using the following methods:
- *
- * <ul>
- * <li>{@link IProperties#setPositivePrefix(String)}
- * <li>{@link IProperties#setPositiveSuffix(String)}
- * <li>{@link IProperties#setNegativePrefix(String)}
- * <li>{@link IProperties#setNegativeSuffix(String)}
- * <li>{@link IProperties#setPositivePrefixPattern(String)}
- * <li>{@link IProperties#setPositiveSuffixPattern(String)}
- * <li>{@link IProperties#setNegativePrefixPattern(String)}
- * <li>{@link IProperties#setNegativeSuffixPattern(String)}
- * </ul>
- *
- * If one of the first four methods is used (those of the form <code>setXxxYyy</code>), the value
- * will be interpreted literally. If one of the second four methods is used (those of the form
- * <code>setXxxYyyPattern</code>), locale-specific symbols for the plus sign, minus sign, percent
- * sign, permille sign, and currency sign will be substituted into the string, according to Unicode
- * Technical Standard #35 (LDML) section 3.2.
- *
- * <p>Literal characters can be used in the <code>setXxxYyyPattern</code> methods by using quotes;
- * for example, to display a literal "%" sign, you can set the pattern <code>'%'</code>. To display
- * a literal quote, use two quotes in a row, like <code>''</code>.
- *
- * <p>If a value is set in both a <code>setXxxYyy</code> method and in the corresponding <code>
- * setXxxYyyPattern</code> method, the one set in <code>setXxxYyy</code> takes precedence.
- *
- * <p>For more information on formatting currencies, see {@link CurrencyFormat}.
- *
- * <p>The parameter is taken by reference by these methods into the property bag, meaning that if a
- * mutable object like StringBuilder is passed, changes to the StringBuilder will be reflected in
- * the property bag. However, upon creation of a finalized formatter object, all prefixes and
- * suffixes will be converted to strings and will stop reflecting changes in the property bag.
- */
-public class PositiveNegativeAffixFormat {
-
- public static interface IProperties {
-
- static String DEFAULT_POSITIVE_PREFIX = null;
-
- /** @see #setPositivePrefix */
- public String getPositivePrefix();
-
- /**
- * Sets the prefix to prepend to positive numbers. The prefix will be interpreted literally. For
- * example, if you set a positive prefix of <code>p</code>, then the number 123 will be
- * formatted as "p123" in the locale <em>en-US</em>.
- *
- * <p>For more information on prefixes and suffixes, see {@link PositiveNegativeAffixFormat}.
- *
- * @param positivePrefix The CharSequence to prepend to positive numbers.
- * @return The property bag, for chaining.
- * @see PositiveNegativeAffixFormat
- * @see #setPositivePrefixPattern
- */
- public IProperties setPositivePrefix(String positivePrefix);
-
- static String DEFAULT_POSITIVE_SUFFIX = null;
-
- /** @see #setPositiveSuffix */
- public String getPositiveSuffix();
-
- /**
- * Sets the suffix to append to positive numbers. The suffix will be interpreted literally. For
- * example, if you set a positive suffix of <code>p</code>, then the number 123 will be
- * formatted as "123p" in the locale <em>en-US</em>.
- *
- * <p>For more information on prefixes and suffixes, see {@link PositiveNegativeAffixFormat}.
- *
- * @param positiveSuffix The CharSequence to append to positive numbers.
- * @return The property bag, for chaining.
- * @see PositiveNegativeAffixFormat
- * @see #setPositiveSuffixPattern
- */
- public IProperties setPositiveSuffix(String positiveSuffix);
-
- static String DEFAULT_NEGATIVE_PREFIX = null;
-
- /** @see #setNegativePrefix */
- public String getNegativePrefix();
-
- /**
- * Sets the prefix to prepend to negative numbers. The prefix will be interpreted literally. For
- * example, if you set a negative prefix of <code>n</code>, then the number -123 will be
- * formatted as "n123" in the locale <em>en-US</em>. Note that if the negative prefix is left unset,
- * the locale's minus sign is used.
- *
- * <p>For more information on prefixes and suffixes, see {@link PositiveNegativeAffixFormat}.
- *
- * @param negativePrefix The CharSequence to prepend to negative numbers.
- * @return The property bag, for chaining.
- * @see PositiveNegativeAffixFormat
- * @see #setNegativePrefixPattern
- */
- public IProperties setNegativePrefix(String negativePrefix);
-
- static String DEFAULT_NEGATIVE_SUFFIX = null;
-
- /** @see #setNegativeSuffix */
- public String getNegativeSuffix();
-
- /**
- * Sets the suffix to append to negative numbers. The suffix will be interpreted literally. For
- * example, if you set a suffix prefix of <code>n</code>, then the number -123 will be formatted
- * as "-123n" in the locale <em>en-US</em>. Note that the minus sign is prepended by default unless
- * otherwise specified in either the pattern string or in one of the {@link #setNegativePrefix}
- * methods.
- *
- * <p>For more information on prefixes and suffixes, see {@link PositiveNegativeAffixFormat}.
- *
- * @param negativeSuffix The CharSequence to append to negative numbers.
- * @return The property bag, for chaining.
- * @see PositiveNegativeAffixFormat
- * @see #setNegativeSuffixPattern
- */
- public IProperties setNegativeSuffix(String negativeSuffix);
-
- static String DEFAULT_POSITIVE_PREFIX_PATTERN = null;
-
- /** @see #setPositivePrefixPattern */
- public String getPositivePrefixPattern();
-
- /**
- * Sets the prefix to prepend to positive numbers. Locale-specific symbols will be substituted
- * into the string according to Unicode Technical Standard #35 (LDML).
- *
- * <p>For more information on prefixes and suffixes, see {@link PositiveNegativeAffixFormat}.
- *
- * @param positivePrefixPattern The CharSequence to prepend to positive numbers after locale
- * symbol substitutions take place.
- * @return The property bag, for chaining.
- * @see PositiveNegativeAffixFormat
- * @see #setPositivePrefix
- */
- public IProperties setPositivePrefixPattern(String positivePrefixPattern);
-
- static String DEFAULT_POSITIVE_SUFFIX_PATTERN = null;
-
- /** @see #setPositiveSuffixPattern */
- public String getPositiveSuffixPattern();
-
- /**
- * Sets the suffix to append to positive numbers. Locale-specific symbols will be substituted
- * into the string according to Unicode Technical Standard #35 (LDML).
- *
- * <p>For more information on prefixes and suffixes, see {@link PositiveNegativeAffixFormat}.
- *
- * @param positiveSuffixPattern The CharSequence to append to positive numbers after locale
- * symbol substitutions take place.
- * @return The property bag, for chaining.
- * @see PositiveNegativeAffixFormat
- * @see #setPositiveSuffix
- */
- public IProperties setPositiveSuffixPattern(String positiveSuffixPattern);
-
- static String DEFAULT_NEGATIVE_PREFIX_PATTERN = null;
-
- /** @see #setNegativePrefixPattern */
- public String getNegativePrefixPattern();
-
- /**
- * Sets the prefix to prepend to negative numbers. Locale-specific symbols will be substituted
- * into the string according to Unicode Technical Standard #35 (LDML).
- *
- * <p>For more information on prefixes and suffixes, see {@link PositiveNegativeAffixFormat}.
- *
- * @param negativePrefixPattern The CharSequence to prepend to negative numbers after locale
- * symbol substitutions take place.
- * @return The property bag, for chaining.
- * @see PositiveNegativeAffixFormat
- * @see #setNegativePrefix
- */
- public IProperties setNegativePrefixPattern(String negativePrefixPattern);
-
- static String DEFAULT_NEGATIVE_SUFFIX_PATTERN = null;
-
- /** @see #setNegativeSuffixPattern */
- public String getNegativeSuffixPattern();
-
- /**
- * Sets the suffix to append to negative numbers. Locale-specific symbols will be substituted
- * into the string according to Unicode Technical Standard #35 (LDML).
- *
- * <p>For more information on prefixes and suffixes, see {@link PositiveNegativeAffixFormat}.
- *
- * @param negativeSuffixPattern The CharSequence to append to negative numbers after locale
- * symbol substitutions take place.
- * @return The property bag, for chaining.
- * @see PositiveNegativeAffixFormat
- * @see #setNegativeSuffix
- */
- public IProperties setNegativeSuffixPattern(String negativeSuffixPattern);
-
- static boolean DEFAULT_PLUS_SIGN_ALWAYS_SHOWN = false;
-
- /** @see #setPlusSignAlwaysShown */
- public boolean getPlusSignAlwaysShown();
-
- /**
- * Sets whether to always display of a plus sign on positive numbers.
- *
- * <p>If the location of the negative sign is specified by the decimal format pattern (or by the
- * negative prefix/suffix pattern methods), a plus sign is substituted into that location, in
- * accordance with Unicode Technical Standard #35 (LDML) section 3.2.1. Otherwise, the plus sign
- * is prepended to the number. For example, if the decimal format pattern <code>#;#-</code> is
- * used, then formatting 123 would result in "123+" in the locale <em>en-US</em>.
- *
- * <p>This method should be used <em>instead of</em> setting the positive prefix/suffix. The
- * behavior is undefined if alwaysShowPlusSign is set but the positive prefix/suffix already
- * contains a plus sign.
- *
- * @param plusSignAlwaysShown Whether positive numbers should display a plus sign.
- * @return The property bag, for chaining.
- */
- public IProperties setPlusSignAlwaysShown(boolean plusSignAlwaysShown);
- }
-
- public static PositiveNegativeAffixModifier getInstance(DecimalFormatSymbols symbols, IProperties properties) {
- PNAffixGenerator pnag = PNAffixGenerator.getThreadLocalInstance();
- PNAffixGenerator.Result result = pnag.getModifiers(symbols, properties);
- return new PositiveNegativeAffixModifier(result.positive, result.negative);
- }
-
- // TODO: Investigate static interface methods (Java 8 only?)
- public static void apply(
- FormatQuantity input,
- ModifierHolder mods,
- DecimalFormatSymbols symbols,
- IProperties properties) {
- PNAffixGenerator pnag = PNAffixGenerator.getThreadLocalInstance();
- PNAffixGenerator.Result result = pnag.getModifiers(symbols, properties);
- if (input.isNegative()) {
- mods.add(result.negative);
- } else {
- mods.add(result.positive);
- }
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-// THIS CLASS IS A PROOF OF CONCEPT ONLY.
-// IT REQUIRES ADDITIONAL DISCUSION ABOUT ITS DESIGN AND IMPLEMENTATION.
-
-package com.ibm.icu.impl.number.formatters;
-
-import java.util.Deque;
-
-import com.ibm.icu.impl.number.Format;
-import com.ibm.icu.impl.number.FormatQuantity;
-import com.ibm.icu.impl.number.ModifierHolder;
-import com.ibm.icu.impl.number.NumberStringBuilder;
-
-public class RangeFormat extends Format {
- // Primary settings
- private final String separator;
-
- // Child formatters
- private final Format left;
- private final Format right;
-
- public RangeFormat(Format left, Format right, String separator) {
- this.separator = separator; // TODO: This would be loaded from locale data.
- this.left = left;
- this.right = right;
-
- if (left == null || right == null) {
- throw new IllegalArgumentException("Both child formatters are required for RangeFormat");
- }
- }
-
- @Override
- public int process(
- Deque<FormatQuantity> inputs,
- ModifierHolder mods,
- NumberStringBuilder string,
- int startIndex) {
- ModifierHolder lMods = new ModifierHolder();
- ModifierHolder rMods = new ModifierHolder();
- int lLen = left.process(inputs, lMods, string, startIndex);
- int rLen = right.process(inputs, rMods, string, startIndex + lLen);
-
- // Bubble up any modifiers that are shared between the two sides
- while (lMods.peekLast() != null && lMods.peekLast() == rMods.peekLast()) {
- mods.add(lMods.removeLast());
- rMods.removeLast();
- }
-
- // Apply the remaining modifiers
- lLen += lMods.applyAll(string, startIndex, startIndex + lLen);
- rLen += rMods.applyAll(string, startIndex + lLen, startIndex + lLen + rLen);
-
- int sLen = string.insert(startIndex + lLen, separator, null);
-
- return lLen + sLen + rLen;
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number.formatters;
-
-import com.ibm.icu.impl.number.Rounder;
-import com.ibm.icu.impl.number.Rounder.IBasicRoundingProperties;
-import com.ibm.icu.impl.number.rounders.IncrementRounder;
-import com.ibm.icu.impl.number.rounders.MagnitudeRounder;
-import com.ibm.icu.impl.number.rounders.NoRounder;
-import com.ibm.icu.impl.number.rounders.SignificantDigitsRounder;
-
-// TODO: Figure out a better place to put these methods.
-
-public class RoundingFormat {
-
- public static interface IProperties
- extends IBasicRoundingProperties,
- IncrementRounder.IProperties,
- MagnitudeRounder.IProperties,
- SignificantDigitsRounder.IProperties {}
-
- public static Rounder getDefaultOrNoRounder(IProperties properties) {
- Rounder candidate = getDefaultOrNull(properties);
- if (candidate == null) {
- candidate = NoRounder.getInstance(properties);
- }
- return candidate;
- }
-
- public static Rounder getDefaultOrNull(IProperties properties) {
- if (SignificantDigitsRounder.useSignificantDigits(properties)) {
- return SignificantDigitsRounder.getInstance(properties);
- } else if (IncrementRounder.useRoundingIncrement(properties)) {
- return IncrementRounder.getInstance(properties);
- } else if (MagnitudeRounder.useFractionFormat(properties)) {
- return MagnitudeRounder.getInstance(properties);
- } else {
- return null;
- }
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number.formatters;
-
-import com.ibm.icu.impl.number.Format;
-import com.ibm.icu.impl.number.FormatQuantity;
-import com.ibm.icu.impl.number.FormatQuantitySelector;
-import com.ibm.icu.impl.number.ModifierHolder;
-import com.ibm.icu.impl.number.Properties;
-import com.ibm.icu.impl.number.Rounder;
-import com.ibm.icu.impl.number.modifiers.ConstantAffixModifier;
-import com.ibm.icu.impl.number.modifiers.PositiveNegativeAffixModifier;
-import com.ibm.icu.impl.number.rounders.IncrementRounder;
-import com.ibm.icu.impl.number.rounders.SignificantDigitsRounder;
-import com.ibm.icu.text.DecimalFormatSymbols;
-import com.ibm.icu.text.NumberFormat;
-
-public class ScientificFormat extends Format.BeforeFormat implements Rounder.MultiplierGenerator {
-
- public static interface IProperties
- extends RoundingFormat.IProperties, CurrencyFormat.IProperties {
-
- static boolean DEFAULT_EXPONENT_SIGN_ALWAYS_SHOWN = false;
-
- /** @see #setExponentSignAlwaysShown */
- public boolean getExponentSignAlwaysShown();
-
- /**
- * Sets whether to show the plus sign in the exponent part of numbers with a zero or positive
- * exponent. For example, the number "1200" with the pattern "0.0E0" would be formatted as
- * "1.2E+3" instead of "1.2E3" in <em>en-US</em>.
- *
- * @param exponentSignAlwaysShown Whether to show the plus sign in positive exponents.
- * @return The property bag, for chaining.
- */
- public IProperties setExponentSignAlwaysShown(boolean exponentSignAlwaysShown);
-
- static int DEFAULT_MINIMUM_EXPONENT_DIGITS = -1;
-
- /** @see #setMinimumExponentDigits */
- public int getMinimumExponentDigits();
-
- /**
- * Sets the minimum number of digits to display in the exponent. For example, the number "1200"
- * with the pattern "0.0E00", which has 2 exponent digits, would be formatted as "1.2E03" in
- * <em>en-US</em>.
- *
- * @param minimumExponentDigits The minimum number of digits to display in the exponent field.
- * @return The property bag, for chaining.
- */
- public IProperties setMinimumExponentDigits(int minimumExponentDigits);
-
- @Override
- public IProperties clone();
- }
-
- public static boolean useScientificNotation(IProperties properties) {
- return properties.getMinimumExponentDigits() != IProperties.DEFAULT_MINIMUM_EXPONENT_DIGITS;
- }
-
- private static final ThreadLocal<Properties> threadLocalProperties =
- new ThreadLocal<Properties>() {
- @Override
- protected Properties initialValue() {
- return new Properties();
- }
- };
-
- public static ScientificFormat getInstance(DecimalFormatSymbols symbols, IProperties properties) {
- // If significant digits or rounding interval are specified through normal means, we use those.
- // Otherwise, we use the special significant digit rules for scientific notation.
- Rounder rounder;
- if (IncrementRounder.useRoundingIncrement(properties)) {
- rounder = IncrementRounder.getInstance(properties);
- } else if (SignificantDigitsRounder.useSignificantDigits(properties)) {
- rounder = SignificantDigitsRounder.getInstance(properties);
- } else {
- Properties rprops = threadLocalProperties.get().clear();
-
- int minInt = properties.getMinimumIntegerDigits();
- int maxInt = properties.getMaximumIntegerDigits();
- int minFrac = properties.getMinimumFractionDigits();
- int maxFrac = properties.getMaximumFractionDigits();
-
- // If currency is in use, pull information from CurrencyUsage.
- if (CurrencyFormat.useCurrency(properties)) {
- // Use rprops as the vehicle (it is still clean)
- CurrencyFormat.populateCurrencyRounderProperties(rprops, symbols, properties);
- minFrac = rprops.getMinimumFractionDigits();
- maxFrac = rprops.getMaximumFractionDigits();
- rprops.clear();
- }
-
- // TODO: Mark/Andy, take a look at this logic and see if it makes sense to you.
- // I fiddled with the settings and fallbacks to make the unit tests pass, but I
- // don't feel that it's the "right way" to do things.
-
- if (minInt < 0) minInt = 0;
- if (maxInt < minInt) maxInt = minInt;
- if (minFrac < 0) minFrac = 0;
- if (maxFrac < minFrac) maxFrac = minFrac;
-
- rprops.setRoundingMode(properties.getRoundingMode());
-
- if (minInt == 0 && maxFrac == 0) {
- // Special case for the pattern "#E0" with no significant digits specified.
- rprops.setMinimumSignificantDigits(1);
- rprops.setMaximumSignificantDigits(Integer.MAX_VALUE);
- } else if (minInt == 0 && minFrac == 0) {
- // Special case for patterns like "#.##E0" with no significant digits specified.
- rprops.setMinimumSignificantDigits(1);
- rprops.setMaximumSignificantDigits(1 + maxFrac);
- } else {
- rprops.setMinimumSignificantDigits(minInt + minFrac);
- rprops.setMaximumSignificantDigits(minInt + maxFrac);
- }
- rprops.setMinimumIntegerDigits(maxInt == 0 ? 0 : Math.max(1, minInt + minFrac - maxFrac));
- rprops.setMaximumIntegerDigits(maxInt);
- rprops.setMinimumFractionDigits(Math.max(0, minFrac + minInt - maxInt));
- rprops.setMaximumFractionDigits(maxFrac);
- rounder = SignificantDigitsRounder.getInstance(rprops);
- }
-
- return new ScientificFormat(symbols, properties, rounder);
- }
-
- public static ScientificFormat getInstance(
- DecimalFormatSymbols symbols, IProperties properties, Rounder rounder) {
- return new ScientificFormat(symbols, properties, rounder);
- }
-
- // Properties
- private final boolean exponentShowPlusSign;
- private final int exponentDigits;
- private final int minInt;
- private final int maxInt;
- private final int interval;
- private final Rounder rounder;
- private final ConstantAffixModifier separatorMod;
- private final PositiveNegativeAffixModifier signMod;
-
- // Symbols
- private final String[] digitStrings;
-
- private ScientificFormat(DecimalFormatSymbols symbols, IProperties properties, Rounder rounder) {
- exponentShowPlusSign = properties.getExponentSignAlwaysShown();
- exponentDigits = Math.max(1, properties.getMinimumExponentDigits());
- int _maxInt = properties.getMaximumIntegerDigits();
- int _minInt = properties.getMinimumIntegerDigits();
- // Special behavior:
- if (_maxInt > 8) {
- _maxInt = _minInt;
- }
- maxInt = _maxInt < 0 ? Integer.MAX_VALUE : _maxInt;
- minInt = _minInt < 0 ? 0 : _minInt < maxInt ? _minInt : maxInt;
- interval = Math.max(1, maxInt);
- this.rounder = rounder;
- digitStrings = symbols.getDigitStrings(); // makes a copy
-
- separatorMod =
- new ConstantAffixModifier(
- "", symbols.getExponentSeparator(), NumberFormat.Field.EXPONENT_SYMBOL, true);
- signMod =
- new PositiveNegativeAffixModifier(
- new ConstantAffixModifier(
- "",
- exponentShowPlusSign ? symbols.getPlusSignString() : "",
- NumberFormat.Field.EXPONENT_SIGN,
- true),
- new ConstantAffixModifier(
- "", symbols.getMinusSignString(), NumberFormat.Field.EXPONENT_SIGN, true));
- }
-
- private static final ThreadLocal<StringBuilder> threadLocalStringBuilder =
- new ThreadLocal<StringBuilder>() {
- @Override
- protected StringBuilder initialValue() {
- return new StringBuilder();
- }
- };
-
- @Override
- public void before(FormatQuantity input, ModifierHolder mods) {
-
- // Treat zero as if it had magnitude 0
- int exponent;
- if (input.isZero()) {
- rounder.apply(input);
- exponent = 0;
- } else {
- exponent = -rounder.chooseMultiplierAndApply(input, this);
- }
-
- // Format the exponent part of the scientific format.
- // Insert digits starting from the left so that append can be used.
- // TODO: Use thread locals here.
- FormatQuantity exponentQ = FormatQuantitySelector.from(exponent);
- StringBuilder exponentSB = threadLocalStringBuilder.get();
- exponentSB.setLength(0);
- exponentQ.setIntegerFractionLength(exponentDigits, Integer.MAX_VALUE, 0, 0);
- for (int i = exponentQ.getUpperDisplayMagnitude(); i >= 0; i--) {
- exponentSB.append(digitStrings[exponentQ.getDigit(i)]);
- }
-
- // Add modifiers from the outside in.
- mods.add(
- new ConstantAffixModifier("", exponentSB.toString(), NumberFormat.Field.EXPONENT, true));
- mods.add(signMod.getModifier(exponent < 0));
- mods.add(separatorMod);
- }
-
- @Override
- public int getMultiplier(int magnitude) {
- int digitsShown = ((magnitude % interval + interval) % interval) + 1;
- if (digitsShown < minInt) {
- digitsShown = minInt;
- } else if (digitsShown > maxInt) {
- digitsShown = maxInt;
- }
- int retval = digitsShown - magnitude - 1;
- return retval;
- }
-
- @Override
- public void export(Properties properties) {
- properties.setMinimumExponentDigits(exponentDigits);
- properties.setExponentSignAlwaysShown(exponentShowPlusSign);
-
- // Set the transformed object into the property bag. This may result in a pattern string that
- // uses different syntax from the original, but it will be functionally equivalent.
- rounder.export(properties);
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number.formatters;
-
-import java.util.Deque;
-
-import com.ibm.icu.impl.number.Format;
-import com.ibm.icu.impl.number.FormatQuantity;
-import com.ibm.icu.impl.number.ModifierHolder;
-import com.ibm.icu.impl.number.NumberStringBuilder;
-import com.ibm.icu.impl.number.Properties;
-
-// TODO: This class isn't currently being used anywhere. Consider removing it.
-
-/** Attaches all prefixes and suffixes at this point in the render tree without bubbling up. */
-public class StrongAffixFormat extends Format implements Format.AfterFormat {
- private final Format child;
-
- public StrongAffixFormat(Format child) {
- this.child = child;
-
- if (child == null) {
- throw new IllegalArgumentException("A child formatter is required for StrongAffixFormat");
- }
- }
-
- @Override
- public int process(
- Deque<FormatQuantity> inputs,
- ModifierHolder mods,
- NumberStringBuilder string,
- int startIndex) {
- int length = child.process(inputs, mods, string, startIndex);
- length += mods.applyAll(string, startIndex, startIndex + length);
- return length;
- }
-
- @Override
- public int after(
- ModifierHolder mods, NumberStringBuilder string, int leftIndex, int rightIndex) {
- return mods.applyAll(string, leftIndex, rightIndex);
- }
-
- @Override
- public void export(Properties properties) {
- // Nothing to do.
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number.modifiers;
-
-import com.ibm.icu.impl.number.Modifier;
-import com.ibm.icu.impl.number.Modifier.AffixModifier;
-import com.ibm.icu.impl.number.NumberStringBuilder;
-import com.ibm.icu.impl.number.Properties;
-import com.ibm.icu.text.NumberFormat.Field;
-
-/** The canonical implementation of {@link Modifier}, containing a prefix and suffix string. */
-public class ConstantAffixModifier extends Modifier.BaseModifier implements AffixModifier {
-
- // TODO: Avoid making a new instance by default if prefix and suffix are empty
- public static final AffixModifier EMPTY = new ConstantAffixModifier();
-
- private final String prefix;
- private final String suffix;
- private final Field field;
- private final boolean strong;
-
- /**
- * Constructs an instance with the given strings.
- *
- * <p>The arguments need to be Strings, not CharSequences, because Strings are immutable but
- * CharSequences are not.
- *
- * @param prefix The prefix string.
- * @param suffix The suffix string.
- * @param field The field type to be associated with this modifier. Can be null.
- * @param strong Whether this modifier should be strongly applied.
- * @see Field
- */
- public ConstantAffixModifier(String prefix, String suffix, Field field, boolean strong) {
- // Use an empty string instead of null if we are given null
- // TODO: Consider returning a null modifier if both prefix and suffix are empty.
- this.prefix = (prefix == null ? "" : prefix);
- this.suffix = (suffix == null ? "" : suffix);
- this.field = field;
- this.strong = strong;
- }
-
- /**
- * Constructs a new instance with an empty prefix, suffix, and field.
- */
- public ConstantAffixModifier() {
- prefix = "";
- suffix = "";
- field = null;
- strong = false;
- }
-
- @Override
- public int apply(NumberStringBuilder output, int leftIndex, int rightIndex) {
- // Insert the suffix first since inserting the prefix will change the rightIndex
- int length = output.insert(rightIndex, suffix, field);
- length += output.insert(leftIndex, prefix, field);
- return length;
- }
-
- @Override
- public int length() {
- return prefix.length() + suffix.length();
- }
-
- @Override
- public boolean isStrong() {
- return strong;
- }
-
- @Override
- public String getPrefix() {
- return prefix;
- }
-
- @Override
- public String getSuffix() {
- return suffix;
- }
-
- public boolean contentEquals(CharSequence _prefix, CharSequence _suffix) {
- if (_prefix == null && !prefix.isEmpty()) return false;
- if (_suffix == null && !suffix.isEmpty()) return false;
- if (prefix.length() != _prefix.length()) return false;
- if (suffix.length() != _suffix.length()) return false;
- for (int i = 0; i < prefix.length(); i++) {
- if (prefix.charAt(i) != _prefix.charAt(i)) return false;
- }
- for (int i = 0; i < suffix.length(); i++) {
- if (suffix.charAt(i) != _suffix.charAt(i)) return false;
- }
- return true;
- }
-
- @Override
- public String toString() {
- return String.format(
- "<ConstantAffixModifier(%d) prefix:'%s' suffix:'%s'>", length(), prefix, suffix);
- }
-
- @Override
- public void export(Properties properties) {
- throw new UnsupportedOperationException();
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number.modifiers;
-
-import com.ibm.icu.impl.number.Modifier;
-import com.ibm.icu.impl.number.Modifier.AffixModifier;
-import com.ibm.icu.impl.number.NumberStringBuilder;
-import com.ibm.icu.impl.number.Properties;
-import com.ibm.icu.text.NumberFormat.Field;
-
-/**
- * An implementation of {@link Modifier} that allows for multiple types of fields in the same
- * modifier. Constructed based on the contents of two {@link NumberStringBuilder} instances (one for
- * the prefix, one for the suffix).
- */
-public class ConstantMultiFieldModifier extends Modifier.BaseModifier implements AffixModifier {
-
- // TODO: Avoid making a new instance by default if prefix and suffix are empty
- public static final ConstantMultiFieldModifier EMPTY = new ConstantMultiFieldModifier();
-
- private final char[] prefixChars;
- private final char[] suffixChars;
- private final Field[] prefixFields;
- private final Field[] suffixFields;
- private final String prefix;
- private final String suffix;
- private final boolean strong;
-
- public ConstantMultiFieldModifier(
- NumberStringBuilder prefix, NumberStringBuilder suffix, boolean strong) {
- prefixChars = prefix.toCharArray();
- suffixChars = suffix.toCharArray();
- prefixFields = prefix.toFieldArray();
- suffixFields = suffix.toFieldArray();
- this.prefix = new String(prefixChars);
- this.suffix = new String(suffixChars);
- this.strong = strong;
- }
-
- private ConstantMultiFieldModifier() {
- prefixChars = new char[0];
- suffixChars = new char[0];
- prefixFields = new Field[0];
- suffixFields = new Field[0];
- prefix = "";
- suffix = "";
- strong = false;
- }
-
- @Override
- public int apply(NumberStringBuilder output, int leftIndex, int rightIndex) {
- // Insert the suffix first since inserting the prefix will change the rightIndex
- int length = output.insert(rightIndex, suffixChars, suffixFields);
- length += output.insert(leftIndex, prefixChars, prefixFields);
- return length;
- }
-
- @Override
- public int length() {
- return prefixChars.length + suffixChars.length;
- }
-
- @Override
- public boolean isStrong() {
- return strong;
- }
-
- @Override
- public String getPrefix() {
- return prefix;
- }
-
- @Override
- public String getSuffix() {
- return suffix;
- }
-
- public boolean contentEquals(NumberStringBuilder prefix, NumberStringBuilder suffix) {
- return prefix.contentEquals(prefixChars, prefixFields)
- && suffix.contentEquals(suffixChars, suffixFields);
- }
-
- @Override
- public String toString() {
- return String.format(
- "<ConstantMultiFieldModifier(%d) prefix:'%s' suffix:'%s'>", length(), prefix, suffix);
- }
-
- @Override
- public void export(Properties properties) {
- throw new UnsupportedOperationException();
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number.modifiers;
-
-import com.ibm.icu.impl.StandardPlural;
-import com.ibm.icu.impl.number.Format;
-import com.ibm.icu.impl.number.FormatQuantity;
-import com.ibm.icu.impl.number.Modifier;
-import com.ibm.icu.impl.number.ModifierHolder;
-import com.ibm.icu.impl.number.Properties;
-import com.ibm.icu.text.PluralRules;
-
-// TODO: Is it okay that this class is not completely immutable? Right now it is internal-only.
-// Freezable or Builder could be used if necessary.
-
-/**
- * A basic implementation of {@link com.ibm.icu.impl.number.Modifier.PositiveNegativePluralModifier}
- * that is built on the fly using its <code>put</code> methods.
- */
-public class GeneralPluralModifier extends Format.BeforeFormat
- implements Modifier.PositiveNegativePluralModifier {
- /**
- * A single array for modifiers. Even elements are positive; odd elements are negative. The
- * elements 2i and 2i+1 belong to the StandardPlural with ordinal i.
- */
- private final Modifier[] mods;
-
- public GeneralPluralModifier() {
- this.mods = new Modifier[StandardPlural.COUNT * 2];
- }
-
- /** Adds a positive/negative-agnostic modifier for the specified plural form. */
- public void put(StandardPlural plural, Modifier modifier) {
- put(plural, modifier, modifier);
- }
-
- /** Adds a positive and a negative modifier for the specified plural form. */
- public void put(StandardPlural plural, Modifier positive, Modifier negative) {
- assert mods[plural.ordinal() * 2] == null;
- assert mods[plural.ordinal() * 2 + 1] == null;
- assert positive != null;
- assert negative != null;
- mods[plural.ordinal() * 2] = positive;
- mods[plural.ordinal() * 2 + 1] = negative;
- }
-
- @Override
- public Modifier getModifier(StandardPlural plural, boolean isNegative) {
- Modifier mod = mods[plural.ordinal() * 2 + (isNegative ? 1 : 0)];
- if (mod == null) {
- mod = mods[StandardPlural.OTHER.ordinal()*2 + (isNegative ? 1 : 0)];
- }
- if (mod == null) {
- throw new UnsupportedOperationException();
- }
- return mod;
- }
-
- @Override
- public void before(FormatQuantity input, ModifierHolder mods, PluralRules rules) {
- mods.add(getModifier(input.getStandardPlural(rules), input.isNegative()));
- }
-
- @Override
- public void before(FormatQuantity input, ModifierHolder mods) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void export(Properties properties) {
- // Since we can export only one affix pair, do the one for "other".
- Modifier positive = getModifier(StandardPlural.OTHER, false);
- Modifier negative = getModifier(StandardPlural.OTHER, true);
- PositiveNegativeAffixModifier.exportPositiveNegative(properties, positive, negative);
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number.modifiers;
-
-import com.ibm.icu.impl.number.Format;
-import com.ibm.icu.impl.number.FormatQuantity;
-import com.ibm.icu.impl.number.Modifier;
-import com.ibm.icu.impl.number.Modifier.AffixModifier;
-import com.ibm.icu.impl.number.ModifierHolder;
-import com.ibm.icu.impl.number.Properties;
-
-/** A class containing a positive form and a negative form of {@link ConstantAffixModifier}. */
-public class PositiveNegativeAffixModifier extends Format.BeforeFormat
- implements Modifier.PositiveNegativeModifier {
- private final AffixModifier positive;
- private final AffixModifier negative;
-
- /**
- * Constructs an instance using the two {@link ConstantMultiFieldModifier} classes for positive
- * and negative.
- *
- * @param positive The positive-form Modifier.
- * @param negative The negative-form Modifier.
- */
- public PositiveNegativeAffixModifier(AffixModifier positive, AffixModifier negative) {
- this.positive = positive;
- this.negative = negative;
- }
-
- @Override
- public Modifier getModifier(boolean isNegative) {
- return isNegative ? negative : positive;
- }
-
- @Override
- public void before(FormatQuantity input, ModifierHolder mods) {
- Modifier mod = getModifier(input.isNegative());
- mods.add(mod);
- }
-
- @Override
- public void export(Properties properties) {
- exportPositiveNegative(properties, positive, negative);
- }
-
- /** Internal method used to export a positive and negative modifier to a property bag. */
- static void exportPositiveNegative(Properties properties, Modifier positive, Modifier negative) {
- properties.setPositivePrefix(positive.getPrefix().isEmpty() ? null : positive.getPrefix());
- properties.setPositiveSuffix(positive.getSuffix().isEmpty() ? null : positive.getSuffix());
- properties.setNegativePrefix(negative.getPrefix().isEmpty() ? null : negative.getPrefix());
- properties.setNegativeSuffix(negative.getSuffix().isEmpty() ? null : negative.getSuffix());
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number.modifiers;
-
-import com.ibm.icu.impl.SimpleFormatterImpl;
-import com.ibm.icu.impl.number.Modifier;
-import com.ibm.icu.impl.number.NumberStringBuilder;
-import com.ibm.icu.impl.number.Properties;
-import com.ibm.icu.text.NumberFormat.Field;
-
-/**
- * The second primary implementation of {@link Modifier}, this one consuming a {@link
- * com.ibm.icu.text.SimpleFormatter} pattern.
- */
-public class SimpleModifier extends Modifier.BaseModifier {
- private final String compiledPattern;
- private final Field field;
- private final boolean strong;
-
- /** Creates a modifier that uses the SimpleFormatter string formats. */
- public SimpleModifier(String compiledPattern, Field field, boolean strong) {
- this.compiledPattern = (compiledPattern == null) ? "\u0001\u0000" : compiledPattern;
- this.field = field;
- this.strong = strong;
- }
-
- @Override
- public int apply(NumberStringBuilder output, int leftIndex, int rightIndex) {
- return formatAsPrefixSuffix(compiledPattern, output, leftIndex, rightIndex, field);
- }
-
- @Override
- public int length() {
- // TODO: Make a separate method for computing the length only?
- return formatAsPrefixSuffix(compiledPattern, null, -1, -1, field);
- }
-
- @Override
- public boolean isStrong() {
- return strong;
- }
-
- @Override
- public String getPrefix() {
- // TODO: Implement this when MeasureFormat is ready.
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String getSuffix() {
- // TODO: Implement this when MeasureFormat is ready.
- throw new UnsupportedOperationException();
- }
-
- /**
- * TODO: This belongs in SimpleFormatterImpl. The only reason I haven't moved it there yet is
- * because DoubleSidedStringBuilder is an internal class and SimpleFormatterImpl feels like it
- * should not depend on it.
- *
- * <p>Formats a value that is already stored inside the StringBuilder <code>result</code> between
- * the indices <code>startIndex</code> and <code>endIndex</code> by inserting characters before
- * the start index and after the end index.
- *
- * <p>This is well-defined only for patterns with exactly one argument.
- *
- * @param compiledPattern Compiled form of a pattern string.
- * @param result The StringBuilder containing the value argument.
- * @param startIndex The left index of the value within the string builder.
- * @param endIndex The right index of the value within the string builder.
- * @return The number of characters (UTF-16 code points) that were added to the StringBuilder.
- */
- public static int formatAsPrefixSuffix(
- String compiledPattern,
- NumberStringBuilder result,
- int startIndex,
- int endIndex,
- Field field) {
- assert SimpleFormatterImpl.getArgumentLimit(compiledPattern) == 1;
- int ARG_NUM_LIMIT = 0x100;
- int length = 0, offset = 2;
- if (compiledPattern.charAt(1) != '\u0000') {
- int prefixLength = compiledPattern.charAt(1) - ARG_NUM_LIMIT;
- if (result != null) {
- result.insert(startIndex, compiledPattern, 2, 2 + prefixLength, field);
- }
- length += prefixLength;
- offset = 3 + prefixLength;
- }
- if (offset < compiledPattern.length()) {
- int suffixLength = compiledPattern.charAt(offset) - ARG_NUM_LIMIT;
- if (result != null) {
- result.insert(
- endIndex + length, compiledPattern, offset + 1, offset + suffixLength + 1, field);
- }
- length += suffixLength;
- }
- return length;
- }
-
- /** TODO: Move this to a test file somewhere, once we figure out what to do with the method. */
- public static void testFormatAsPrefixSuffix() {
- String[] patterns = {"{0}", "X{0}Y", "XX{0}YYY", "{0}YY", "XXXX{0}"};
- Object[][] outputs = {{"", 0, 0}, {"abcde", 0, 0}, {"abcde", 2, 2}, {"abcde", 1, 3}};
- String[][] expecteds = {
- {"", "XY", "XXYYY", "YY", "XXXX"},
- {"abcde", "XYabcde", "XXYYYabcde", "YYabcde", "XXXXabcde"},
- {"abcde", "abXYcde", "abXXYYYcde", "abYYcde", "abXXXXcde"},
- {"abcde", "aXbcYde", "aXXbcYYYde", "abcYYde", "aXXXXbcde"}
- };
- for (int i = 0; i < patterns.length; i++) {
- for (int j = 0; j < outputs.length; j++) {
- String pattern = patterns[i];
- String compiledPattern =
- SimpleFormatterImpl.compileToStringMinMaxArguments(pattern, new StringBuilder(), 1, 1);
- NumberStringBuilder output = new NumberStringBuilder();
- output.append((String) outputs[j][0], null);
- formatAsPrefixSuffix(
- compiledPattern, output, (Integer) outputs[j][1], (Integer) outputs[j][2], null);
- String expected = expecteds[j][i];
- String actual = output.toString();
- assert expected.equals(actual);
- }
- }
- }
-
- @Override
- public void export(Properties properties) {
- throw new UnsupportedOperationException();
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number.rounders;
-
-import java.math.BigDecimal;
-
-import com.ibm.icu.impl.number.FormatQuantity;
-import com.ibm.icu.impl.number.Properties;
-import com.ibm.icu.impl.number.Rounder;
-
-public class IncrementRounder extends Rounder {
-
- public static interface IProperties extends IBasicRoundingProperties {
-
- static BigDecimal DEFAULT_ROUNDING_INCREMENT = null;
-
- /** @see #setRoundingIncrement */
- public BigDecimal getRoundingIncrement();
-
- /**
- * Sets the increment to which to round numbers. For example, with a rounding interval of 0.05,
- * the number 11.17 would be formatted as "11.15" in locale <em>en-US</em> with the default
- * rounding mode.
- *
- * <p>You can use either a rounding increment or significant digits, but not both at the same
- * time.
- *
- * <p>The rounding increment can be specified in a pattern string. For example, the pattern
- * "#,##0.05" corresponds to a rounding interval of 0.05 with 1 minimum integer digit and a
- * grouping size of 3.
- *
- * @param roundingIncrement The interval to which to round.
- * @return The property bag, for chaining.
- */
- public IProperties setRoundingIncrement(BigDecimal roundingIncrement);
- }
-
- public static boolean useRoundingIncrement(IProperties properties) {
- return properties.getRoundingIncrement() != IProperties.DEFAULT_ROUNDING_INCREMENT;
- }
-
- private final BigDecimal roundingIncrement;
-
- public static IncrementRounder getInstance(IProperties properties) {
- return new IncrementRounder(properties);
- }
-
- private IncrementRounder(IProperties properties) {
- super(properties);
- if (properties.getRoundingIncrement().compareTo(BigDecimal.ZERO) <= 0) {
- throw new IllegalArgumentException("Rounding interval must be greater than zero");
- }
- roundingIncrement = properties.getRoundingIncrement();
- }
-
- @Override
- public void apply(FormatQuantity input) {
- input.roundToIncrement(roundingIncrement, mathContext);
- applyDefaults(input);
- }
-
- @Override
- public void export(Properties properties) {
- super.export(properties);
- properties.setRoundingIncrement(roundingIncrement);
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number.rounders;
-
-import com.ibm.icu.impl.number.FormatQuantity;
-import com.ibm.icu.impl.number.Rounder;
-
-public class MagnitudeRounder extends Rounder {
-
- public static interface IProperties extends IBasicRoundingProperties {}
-
- public static boolean useFractionFormat(IProperties properties) {
- return properties.getMinimumFractionDigits() != IProperties.DEFAULT_MINIMUM_FRACTION_DIGITS
- || properties.getMaximumFractionDigits() != IProperties.DEFAULT_MAXIMUM_FRACTION_DIGITS;
- }
-
- public static MagnitudeRounder getInstance(IBasicRoundingProperties properties) {
- return new MagnitudeRounder(properties);
- }
-
- private MagnitudeRounder(IBasicRoundingProperties properties) {
- super(properties);
- }
-
- @Override
- public void apply(FormatQuantity input) {
- input.roundToMagnitude(-maxFrac, mathContext);
- applyDefaults(input);
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number.rounders;
-
-import com.ibm.icu.impl.number.FormatQuantity;
-import com.ibm.icu.impl.number.Rounder;
-
-/** Sets the integer and fraction length based on the properties, but does not perform rounding. */
-public final class NoRounder extends Rounder {
-
- public static NoRounder getInstance(IBasicRoundingProperties properties) {
- return new NoRounder(properties);
- }
-
- private NoRounder(IBasicRoundingProperties properties) {
- super(properties);
- }
-
- @Override
- public void apply(FormatQuantity input) {
- applyDefaults(input);
- input.roundToInfinity();
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.impl.number.rounders;
-
-import java.math.RoundingMode;
-
-import com.ibm.icu.impl.number.FormatQuantity;
-import com.ibm.icu.impl.number.Properties;
-import com.ibm.icu.impl.number.Rounder;
-
-public class SignificantDigitsRounder extends Rounder {
-
- /**
- * Sets whether the minimum significant digits should override the maximum integer and fraction
- * digits. This affects both display and rounding. Default is true.
- *
- * <p>For example, if this option is enabled, formatting the number 4.567 with 3 min/max
- * significant digits against the pattern "0.0" (1 min/max fraction digits) will result in "4.57"
- * in locale <em>en-US</em> with the default rounding mode. If this option is disabled, the max
- * fraction digits take priority instead, and the output will be "4.6".
- *
- * @param significantDigitsOverride true to ensure that the minimum significant digits are always
- * shown; false to ensure that the maximum integer and fraction digits are obeyed.
- * @return The property bag, for chaining.
- */
- public static enum SignificantDigitsMode {
- OVERRIDE_MAXIMUM_FRACTION,
- RESPECT_MAXIMUM_FRACTION,
- ENSURE_MINIMUM_SIGNIFICANT
- };
-
- public static interface IProperties extends IBasicRoundingProperties {
-
- static int DEFAULT_MINIMUM_SIGNIFICANT_DIGITS = -1;
-
- /** @see #setMinimumSignificantDigits */
- public int getMinimumSignificantDigits();
-
- /**
- * Sets the minimum number of significant digits to display. If, after rounding to the number of
- * significant digits specified by {@link #setMaximumSignificantDigits}, the number of remaining
- * significant digits is less than the minimum, the number will be padded with zeros. For
- * example, if minimum significant digits is 3, the number 5.8 will be formatted as "5.80" in
- * locale <em>en-US</em>. Note that minimum significant digits is relevant only when numbers
- * have digits after the decimal point.
- *
- * <p>If both minimum significant digits and minimum integer/fraction digits are set at the same
- * time, both values will be respected, and the one that results in the greater number of
- * padding zeros will be used. For example, formatting the number 73 with 3 minimum significant
- * digits and 2 minimum fraction digits will produce "73.00".
- *
- * <p>The number of significant digits can be specified in a pattern string using the '@'
- * character. For example, the pattern "@@#" corresponds to a minimum of 2 and a maximum of 3
- * significant digits.
- *
- * @param minimumSignificantDigits The minimum number of significant digits to display.
- * @return The property bag, for chaining.
- */
- public IProperties setMinimumSignificantDigits(int minimumSignificantDigits);
-
- static int DEFAULT_MAXIMUM_SIGNIFICANT_DIGITS = -1;
-
- /** @see #setMaximumSignificantDigits */
- public int getMaximumSignificantDigits();
-
- /**
- * Sets the maximum number of significant digits to display. The number of significant digits is
- * equal to the number of digits counted from the leftmost nonzero digit through the rightmost
- * nonzero digit; for example, the number "2010" has 3 significant digits. If the number has
- * more significant digits than specified here, the extra significant digits will be rounded off
- * using the rounding mode specified by {@link #setRoundingMode(RoundingMode)}. For example, if
- * maximum significant digits is 3, the number 1234.56 will be formatted as "1230" in locale
- * <em>en-US</em> with the default rounding mode.
- *
- * <p>If both maximum significant digits and maximum integer/fraction digits are set at the same
- * time, the behavior is undefined.
- *
- * <p>The number of significant digits can be specified in a pattern string using the '@'
- * character. For example, the pattern "@@#" corresponds to a minimum of 2 and a maximum of 3
- * significant digits.
- *
- * @param maximumSignificantDigits The maximum number of significant digits to display.
- * @return The property bag, for chaining.
- */
- public IProperties setMaximumSignificantDigits(int maximumSignificantDigits);
-
- static SignificantDigitsMode DEFAULT_SIGNIFICANT_DIGITS_MODE = null;
-
- /** @see #setSignificantDigitsMode */
- public SignificantDigitsMode getSignificantDigitsMode();
-
- /**
- * Sets the strategy used when reconciling significant digits versus integer and fraction
- * lengths.
- *
- * @param significantDigitsMode One of the options from {@link SignificantDigitsMode}.
- * @return The property bag, for chaining.
- */
- public IProperties setSignificantDigitsMode(SignificantDigitsMode significantDigitsMode);
- }
-
- public static boolean useSignificantDigits(IProperties properties) {
- return properties.getMinimumSignificantDigits()
- != IProperties.DEFAULT_MINIMUM_SIGNIFICANT_DIGITS
- || properties.getMaximumSignificantDigits()
- != IProperties.DEFAULT_MAXIMUM_SIGNIFICANT_DIGITS
- || properties.getSignificantDigitsMode() != IProperties.DEFAULT_SIGNIFICANT_DIGITS_MODE;
- }
-
- public static SignificantDigitsRounder getInstance(IProperties properties) {
- return new SignificantDigitsRounder(properties);
- }
-
- private final int minSig;
- private final int maxSig;
- private final SignificantDigitsMode mode;
-
- private SignificantDigitsRounder(IProperties properties) {
- super(properties);
- int _minSig = properties.getMinimumSignificantDigits();
- int _maxSig = properties.getMaximumSignificantDigits();
- minSig = _minSig < 1 ? 1 : _minSig > 1000 ? 1000 : _minSig;
- maxSig = _maxSig < 0 ? 1000 : _maxSig < minSig ? minSig : _maxSig > 1000 ? 1000 : _maxSig;
- SignificantDigitsMode _mode = properties.getSignificantDigitsMode();
- mode = _mode == null ? SignificantDigitsMode.OVERRIDE_MAXIMUM_FRACTION : _mode;
- }
-
- @Override
- public void apply(FormatQuantity input) {
-
- int magnitude, effectiveMag, magMinSig, magMaxSig;
-
- if (input.isZero()) {
- // Treat zero as if magnitude corresponded to the minimum number of zeros
- magnitude = minInt - 1;
- } else {
- magnitude = input.getMagnitude();
- }
- effectiveMag = Math.min(magnitude + 1, maxInt);
- magMinSig = effectiveMag - minSig;
- magMaxSig = effectiveMag - maxSig;
-
- // Step 1: pick the rounding magnitude and apply.
- int roundingMagnitude;
- switch (mode) {
- case OVERRIDE_MAXIMUM_FRACTION:
- // Always round to maxSig.
- // Of the six possible orders:
- // Case 1: minSig, maxSig, minFrac, maxFrac -- maxSig wins
- // Case 2: minSig, minFrac, maxSig, maxFrac -- maxSig wins
- // Case 3: minSig, minFrac, maxFrac, maxSig -- maxSig wins
- // Case 4: minFrac, minSig, maxSig, maxFrac -- maxSig wins
- // Case 5: minFrac, minSig, maxFrac, maxSig -- maxSig wins
- // Case 6: minFrac, maxFrac, minSig, maxSig -- maxSig wins
- roundingMagnitude = magMaxSig;
- break;
- case RESPECT_MAXIMUM_FRACTION:
- // Round to the strongest of maxFrac, maxInt, and maxSig.
- // Of the six possible orders:
- // Case 1: minSig, maxSig, minFrac, maxFrac -- maxSig wins
- // Case 2: minSig, minFrac, maxSig, maxFrac -- maxSig wins
- // Case 3: minSig, minFrac, maxFrac, maxSig -- maxFrac wins --> differs from default
- // Case 4: minFrac, minSig, maxSig, maxFrac -- maxSig wins
- // Case 5: minFrac, minSig, maxFrac, maxSig -- maxFrac wins --> differs from default
- // Case 6: minFrac, maxFrac, minSig, maxSig -- maxFrac wins --> differs from default
- //
- // Math.max() picks the rounding magnitude farthest to the left (most significant).
- // Math.min() picks the rounding magnitude farthest to the right (least significant).
- roundingMagnitude = Math.max(-maxFrac, magMaxSig);
- break;
- case ENSURE_MINIMUM_SIGNIFICANT:
- // Round to the strongest of maxFrac and maxSig, and always ensure minSig.
- // Of the six possible orders:
- // Case 1: minSig, maxSig, minFrac, maxFrac -- maxSig wins
- // Case 2: minSig, minFrac, maxSig, maxFrac -- maxSig wins
- // Case 3: minSig, minFrac, maxFrac, maxSig -- maxFrac wins --> differs from default
- // Case 4: minFrac, minSig, maxSig, maxFrac -- maxSig wins
- // Case 5: minFrac, minSig, maxFrac, maxSig -- maxFrac wins --> differs from default
- // Case 6: minFrac, maxFrac, minSig, maxSig -- minSig wins --> differs from default
- roundingMagnitude = Math.min(magMinSig, Math.max(-maxFrac, magMaxSig));
- break;
- default:
- throw new AssertionError();
- }
- input.roundToMagnitude(roundingMagnitude, mathContext);
-
- // In case magnitude changed:
- if (input.isZero()) {
- magnitude = minInt - 1;
- } else {
- magnitude = input.getMagnitude();
- }
- effectiveMag = Math.min(magnitude + 1, maxInt);
- magMinSig = effectiveMag - minSig;
- magMaxSig = effectiveMag - maxSig;
-
- // Step 2: pick the number of visible digits.
- switch (mode) {
- case OVERRIDE_MAXIMUM_FRACTION:
- // Ensure minSig is always displayed.
- input.setIntegerFractionLength(
- minInt, maxInt, Math.max(minFrac, -magMinSig), Integer.MAX_VALUE);
- break;
- case RESPECT_MAXIMUM_FRACTION:
- // Ensure minSig is displayed, unless doing so is in violation of maxFrac.
- input.setIntegerFractionLength(
- minInt, maxInt, Math.min(maxFrac, Math.max(minFrac, -magMinSig)), maxFrac);
- break;
- case ENSURE_MINIMUM_SIGNIFICANT:
- // Follow minInt/minFrac, but ensure all digits are allowed to be visible.
- input.setIntegerFractionLength(minInt, maxInt, minFrac, Integer.MAX_VALUE);
- break;
- }
- }
-
- @Override
- public void export(Properties properties) {
- super.export(properties);
- properties.setMinimumSignificantDigits(minSig);
- properties.setMaximumSignificantDigits(maxSig);
- properties.setSignificantDigitsMode(mode);
- }
-}
--- /dev/null
+// © 2016 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+/*
+ *******************************************************************************
+ * Copyright (C) 2012-2016, International Business Machines Corporation and
+ * others. All Rights Reserved.
+ *******************************************************************************
+ */
+package com.ibm.icu.text;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.MissingResourceException;
+
+import com.ibm.icu.impl.ICUCache;
+import com.ibm.icu.impl.ICUData;
+import com.ibm.icu.impl.ICUResourceBundle;
+import com.ibm.icu.impl.SimpleCache;
+import com.ibm.icu.impl.UResource;
+import com.ibm.icu.text.DecimalFormat.Unit;
+import com.ibm.icu.util.ULocale;
+import com.ibm.icu.util.UResourceBundle;
+
+/**
+ * A cache containing data by locale for {@link CompactDecimalFormat}
+ *
+ * @author Travis Keep
+ */
+class CompactDecimalDataCache {
+
+ private static final String SHORT_STYLE = "short";
+ private static final String LONG_STYLE = "long";
+ private static final String SHORT_CURRENCY_STYLE = "shortCurrency";
+ private static final String NUMBER_ELEMENTS = "NumberElements";
+ private static final String PATTERNS_LONG = "patternsLong";
+ private static final String PATTERNS_SHORT = "patternsShort";
+ private static final String DECIMAL_FORMAT = "decimalFormat";
+ private static final String CURRENCY_FORMAT = "currencyFormat";
+ private static final String LATIN_NUMBERING_SYSTEM = "latn";
+
+ private static enum PatternsTableKey { PATTERNS_LONG, PATTERNS_SHORT };
+ private static enum FormatsTableKey { DECIMAL_FORMAT, CURRENCY_FORMAT };
+
+ public static final String OTHER = "other";
+
+ /**
+ * We can specify prefixes or suffixes for values with up to 15 digits,
+ * less than 10^15.
+ */
+ static final int MAX_DIGITS = 15;
+
+ private final ICUCache<ULocale, DataBundle> cache =
+ new SimpleCache<ULocale, DataBundle>();
+
+ /**
+ * Data contains the compact decimal data for a particular locale. Data consists
+ * of one array and two hashmaps. The index of the divisors array as well
+ * as the arrays stored in the values of the two hashmaps correspond
+ * to log10 of the number being formatted, so when formatting 12,345, the 4th
+ * index of the arrays should be used. Divisors contain the number to divide
+ * by before doing formatting. In the case of english, <code>divisors[4]</code>
+ * is 1000. So to format 12,345, divide by 1000 to get 12. Then use
+ * PluralRules with the current locale to figure out which of the 6 plural variants
+ * 12 matches: "zero", "one", "two", "few", "many", or "other." Prefixes and
+ * suffixes are maps whose key is the plural variant and whose values are
+ * arrays of strings with indexes corresponding to log10 of the original number.
+ * these arrays contain the prefix or suffix to use.
+ *
+ * Each array in data is 15 in length, and every index is filled.
+ *
+ * @author Travis Keep
+ *
+ */
+ static class Data {
+ long[] divisors;
+ Map<String, DecimalFormat.Unit[]> units;
+ boolean fromFallback;
+
+ Data(long[] divisors, Map<String, DecimalFormat.Unit[]> units)
+ {
+ this.divisors = divisors;
+ this.units = units;
+ }
+
+ public boolean isEmpty() {
+ return units == null || units.isEmpty();
+ }
+ }
+
+ /**
+ * DataBundle contains compact decimal data for all the styles in a particular
+ * locale. Currently available styles are short and long for decimals, and
+ * short only for currencies.
+ *
+ * @author Travis Keep
+ */
+ static class DataBundle {
+ Data shortData;
+ Data longData;
+ Data shortCurrencyData;
+
+ private DataBundle(Data shortData, Data longData, Data shortCurrencyData) {
+ this.shortData = shortData;
+ this.longData = longData;
+ this.shortCurrencyData = shortCurrencyData;
+ }
+
+ private static DataBundle createEmpty() {
+ return new DataBundle(
+ new Data(new long[MAX_DIGITS], new HashMap<String, DecimalFormat.Unit[]>()),
+ new Data(new long[MAX_DIGITS], new HashMap<String, DecimalFormat.Unit[]>()),
+ new Data(new long[MAX_DIGITS], new HashMap<String, DecimalFormat.Unit[]>())
+ );
+ }
+ }
+
+ /**
+ * Sink for enumerating all of the compact decimal format patterns.
+ *
+ * More specific bundles (en_GB) are enumerated before their parents (en_001, en, root):
+ * Only store a value if it is still missing, that is, it has not been overridden.
+ */
+ private static final class CompactDecimalDataSink extends UResource.Sink {
+
+ private DataBundle dataBundle; // Where to save values when they are read
+ private ULocale locale; // The locale we are traversing (for exception messages)
+ private boolean isLatin; // Whether or not we are traversing the Latin table
+ private boolean isFallback; // Whether or not we are traversing the Latin table as fallback
+
+ /*
+ * NumberElements{ <-- top (numbering system table)
+ * latn{ <-- patternsTable (one per numbering system)
+ * patternsLong{ <-- formatsTable (one per pattern)
+ * decimalFormat{ <-- powersOfTenTable (one per format)
+ * 1000{ <-- pluralVariantsTable (one per power of ten)
+ * one{"0 thousand"} <-- plural variant and template
+ */
+
+ public CompactDecimalDataSink(DataBundle dataBundle, ULocale locale) {
+ this.dataBundle = dataBundle;
+ this.locale = locale;
+ }
+
+ @Override
+ public void put(UResource.Key key, UResource.Value value, boolean isRoot) {
+ // SPECIAL CASE: Don't consume root in the non-Latin numbering system
+ if (isRoot && !isLatin) { return; }
+
+ UResource.Table patternsTable = value.getTable();
+ for (int i1 = 0; patternsTable.getKeyAndValue(i1, key, value); ++i1) {
+
+ // patterns table: check for patternsShort or patternsLong
+ PatternsTableKey patternsTableKey;
+ if (key.contentEquals(PATTERNS_SHORT)) {
+ patternsTableKey = PatternsTableKey.PATTERNS_SHORT;
+ } else if (key.contentEquals(PATTERNS_LONG)) {
+ patternsTableKey = PatternsTableKey.PATTERNS_LONG;
+ } else {
+ continue;
+ }
+
+ // traverse into the table of formats
+ UResource.Table formatsTable = value.getTable();
+ for (int i2 = 0; formatsTable.getKeyAndValue(i2, key, value); ++i2) {
+
+ // formats table: check for decimalFormat or currencyFormat
+ FormatsTableKey formatsTableKey;
+ if (key.contentEquals(DECIMAL_FORMAT)) {
+ formatsTableKey = FormatsTableKey.DECIMAL_FORMAT;
+ } else if (key.contentEquals(CURRENCY_FORMAT)) {
+ formatsTableKey = FormatsTableKey.CURRENCY_FORMAT;
+ } else {
+ continue;
+ }
+
+ // Set the current style and destination based on the lvl1 and lvl2 keys
+ String style = null;
+ Data destination = null;
+ if (patternsTableKey == PatternsTableKey.PATTERNS_LONG
+ && formatsTableKey == FormatsTableKey.DECIMAL_FORMAT) {
+ style = LONG_STYLE;
+ destination = dataBundle.longData;
+ } else if (patternsTableKey == PatternsTableKey.PATTERNS_SHORT
+ && formatsTableKey == FormatsTableKey.DECIMAL_FORMAT) {
+ style = SHORT_STYLE;
+ destination = dataBundle.shortData;
+ } else if (patternsTableKey == PatternsTableKey.PATTERNS_SHORT
+ && formatsTableKey == FormatsTableKey.CURRENCY_FORMAT) {
+ style = SHORT_CURRENCY_STYLE;
+ destination = dataBundle.shortCurrencyData;
+ } else {
+ // Silently ignore this case
+ continue;
+ }
+
+ // SPECIAL CASE: RULES FOR WHETHER OR NOT TO CONSUME THIS TABLE:
+ // 1) Don't consume longData if shortData was consumed from the non-Latin
+ // locale numbering system
+ // 2) Don't consume longData for the first time if this is the root bundle and
+ // shortData is already populated from a more specific locale. Note that if
+ // both longData and shortData are both only in root, longData will be
+ // consumed since it is alphabetically before shortData in the bundle.
+ if (isFallback
+ && style == LONG_STYLE
+ && !dataBundle.shortData.isEmpty()
+ && !dataBundle.shortData.fromFallback) {
+ continue;
+ }
+ if (isRoot
+ && style == LONG_STYLE
+ && dataBundle.longData.isEmpty()
+ && !dataBundle.shortData.isEmpty()) {
+ continue;
+ }
+
+ // Set the "fromFallback" flag on the data object
+ destination.fromFallback = isFallback;
+
+ // traverse into the table of powers of ten
+ UResource.Table powersOfTenTable = value.getTable();
+ for (int i3 = 0; powersOfTenTable.getKeyAndValue(i3, key, value); ++i3) {
+
+ // This value will always be some even power of 10. e.g 10000.
+ long power10 = Long.parseLong(key.toString());
+ int log10Value = (int) Math.log10(power10);
+
+ // Silently ignore divisors that are too big.
+ if (log10Value >= MAX_DIGITS) continue;
+
+ // Iterate over the plural variants ("one", "other", etc)
+ UResource.Table pluralVariantsTable = value.getTable();
+ for (int i4 = 0; pluralVariantsTable.getKeyAndValue(i4, key, value); ++i4) {
+ // TODO: Use StandardPlural rather than String.
+ String pluralVariant = key.toString();
+ String template = value.toString();
+
+ // Copy the data into the in-memory data bundle (do not overwrite
+ // existing values)
+ int numZeros = populatePrefixSuffix(
+ pluralVariant, log10Value, template, locale, style, destination, false);
+
+ // If populatePrefixSuffix returns -1, it means that this key has been
+ // encountered already.
+ if (numZeros < 0) {
+ continue;
+ }
+
+ // Set the divisor, which is based on the number of zeros in the template
+ // string. If the divisor from here is different from the one previously
+ // stored, it means that the number of zeros in different plural variants
+ // differs; throw an exception.
+ long divisor = calculateDivisor(power10, numZeros);
+ if (destination.divisors[log10Value] != 0L
+ && destination.divisors[log10Value] != divisor) {
+ throw new IllegalArgumentException("Plural variant '" + pluralVariant
+ + "' template '" + template
+ + "' for 10^" + log10Value
+ + " has wrong number of zeros in " + localeAndStyle(locale, style));
+ }
+ destination.divisors[log10Value] = divisor;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Fetch data for a particular locale. Clients must not modify any part of the returned data. Portions of returned
+ * data may be shared so modifying it will have unpredictable results.
+ */
+ DataBundle get(ULocale locale) {
+ DataBundle result = cache.get(locale);
+ if (result == null) {
+ result = load(locale);
+ cache.put(locale, result);
+ }
+ return result;
+ }
+
+ private static DataBundle load(ULocale ulocale) throws MissingResourceException {
+ DataBundle dataBundle = DataBundle.createEmpty();
+ String nsName = NumberingSystem.getInstance(ulocale).getName();
+ ICUResourceBundle r = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME,
+ ulocale);
+ CompactDecimalDataSink sink = new CompactDecimalDataSink(dataBundle, ulocale);
+ sink.isFallback = false;
+
+ // First load the number elements data from nsName if nsName is not Latin.
+ if (!nsName.equals(LATIN_NUMBERING_SYSTEM)) {
+ sink.isLatin = false;
+
+ try {
+ r.getAllItemsWithFallback(NUMBER_ELEMENTS + "/" + nsName, sink);
+ } catch (MissingResourceException e) {
+ // Silently ignore and use Latin
+ }
+
+ // Set the "isFallback" flag for when we read Latin
+ sink.isFallback = true;
+ }
+
+ // Now load Latin, which will fill in things that were left out from above.
+ sink.isLatin = true;
+ r.getAllItemsWithFallback(NUMBER_ELEMENTS + "/" + LATIN_NUMBERING_SYSTEM, sink);
+
+ // If longData is empty, default it to be equal to shortData
+ if (dataBundle.longData.isEmpty()) {
+ dataBundle.longData = dataBundle.shortData;
+ }
+
+ // Check for "other" variants in each of the three data classes
+ checkForOtherVariants(dataBundle.longData, ulocale, LONG_STYLE);
+ checkForOtherVariants(dataBundle.shortData, ulocale, SHORT_STYLE);
+ checkForOtherVariants(dataBundle.shortCurrencyData, ulocale, SHORT_CURRENCY_STYLE);
+
+ // Resolve missing elements
+ fillInMissing(dataBundle.longData);
+ fillInMissing(dataBundle.shortData);
+ fillInMissing(dataBundle.shortCurrencyData);
+
+ // Return the data bundle
+ return dataBundle;
+ }
+
+
+ /**
+ * Populates prefix and suffix information for a particular plural variant
+ * and index (log10 value).
+ * @param pluralVariant e.g "one", "other"
+ * @param idx the index (log10 value of the number) 0 <= idx < MAX_DIGITS
+ * @param template e.g "00K"
+ * @param locale the locale
+ * @param style the style
+ * @param destination Extracted prefix and suffix stored here.
+ * @return number of zeros found before any decimal point in template, or -1 if it was not saved.
+ */
+ private static int populatePrefixSuffix(
+ String pluralVariant, int idx, String template, ULocale locale, String style,
+ Data destination, boolean overwrite) {
+ int firstIdx = template.indexOf("0");
+ int lastIdx = template.lastIndexOf("0");
+ if (firstIdx == -1) {
+ throw new IllegalArgumentException(
+ "Expect at least one zero in template '" + template +
+ "' for variant '" +pluralVariant + "' for 10^" + idx +
+ " in " + localeAndStyle(locale, style));
+ }
+ String prefix = template.substring(0, firstIdx);
+ String suffix = template.substring(lastIdx + 1);
+
+ // Save the unit, and return -1 if it was not saved
+ boolean saved = saveUnit(new DecimalFormat.Unit(prefix, suffix), pluralVariant, idx, destination.units, overwrite);
+ if (!saved) {
+ return -1;
+ }
+
+ // If there is effectively no prefix or suffix, ignore the actual
+ // number of 0's and act as if the number of 0's matches the size
+ // of the number
+ if (prefix.trim().length() == 0 && suffix.trim().length() == 0) {
+ return idx + 1;
+ }
+
+ // Calculate number of zeros before decimal point.
+ int i = firstIdx + 1;
+ while (i <= lastIdx && template.charAt(i) == '0') {
+ i++;
+ }
+ return i - firstIdx;
+ }
+
+ /**
+ * Calculate a divisor based on the magnitude and number of zeros in the
+ * template string.
+ * @param power10
+ * @param numZeros
+ * @return
+ */
+ private static long calculateDivisor(long power10, int numZeros) {
+ // We craft our divisor such that when we divide by it, we get a
+ // number with the same number of digits as zeros found in the
+ // plural variant templates. If our magnitude is 10000 and we have
+ // two 0's in our plural variants, then we want a divisor of 1000.
+ // Note that if we have 43560 which is of same magnitude as 10000.
+ // When we divide by 1000 we a quotient which rounds to 44 (2 digits)
+ long divisor = power10;
+ for (int i = 1; i < numZeros; i++) {
+ divisor /= 10;
+ }
+ return divisor;
+ }
+
+
+ /**
+ * Returns locale and style. Used to form useful messages in thrown exceptions.
+ *
+ * Note: This is not covered by unit tests since no exceptions are thrown on the default CLDR data. It is too
+ * cumbersome to cover via reflection.
+ *
+ * @param locale the locale
+ * @param style the style
+ */
+ private static String localeAndStyle(ULocale locale, String style) {
+ return "locale '" + locale + "' style '" + style + "'";
+ }
+
+ /**
+ * Checks to make sure that an "other" variant is present in all powers of 10.
+ * @param data
+ */
+ private static void checkForOtherVariants(Data data, ULocale locale, String style) {
+ DecimalFormat.Unit[] otherByBase = data.units.get(OTHER);
+
+ if (otherByBase == null) {
+ throw new IllegalArgumentException("No 'other' plural variants defined in "
+ + localeAndStyle(locale, style));
+ }
+
+ // Check all other plural variants, and make sure that if any of them are populated, then
+ // other is also populated
+ for (Map.Entry<String, Unit[]> entry : data.units.entrySet()) {
+ if (entry.getKey() == OTHER) continue;
+ DecimalFormat.Unit[] variantByBase = entry.getValue();
+ for (int log10Value = 0; log10Value < MAX_DIGITS; log10Value++) {
+ if (variantByBase[log10Value] != null && otherByBase[log10Value] == null) {
+ throw new IllegalArgumentException(
+ "No 'other' plural variant defined for 10^" + log10Value
+ + " but a '" + entry.getKey() + "' variant is defined"
+ + " in " +localeAndStyle(locale, style));
+ }
+ }
+ }
+ }
+
+ /**
+ * After reading information from resource bundle into a Data object, there
+ * is guarantee that it is complete.
+ *
+ * This method fixes any incomplete data it finds within <code>result</code>.
+ * It looks at each log10 value applying the two rules.
+ * <p>
+ * If no prefix is defined for the "other" variant, use the divisor, prefixes and
+ * suffixes for all defined variants from the previous log10. For log10 = 0,
+ * use all empty prefixes and suffixes and a divisor of 1.
+ * </p><p>
+ * Otherwise, examine each plural variant defined for the given log10 value.
+ * If it has no prefix and suffix for a particular variant, use the one from the
+ * "other" variant.
+ * </p>
+ *
+ * @param result this instance is fixed in-place.
+ */
+ private static void fillInMissing(Data result) {
+ // Initially we assume that previous divisor is 1 with no prefix or suffix.
+ long lastDivisor = 1L;
+ for (int i = 0; i < result.divisors.length; i++) {
+ if (result.units.get(OTHER)[i] == null) {
+ result.divisors[i] = lastDivisor;
+ copyFromPreviousIndex(i, result.units);
+ } else {
+ lastDivisor = result.divisors[i];
+ propagateOtherToMissing(i, result.units);
+ }
+ }
+ }
+
+ private static void propagateOtherToMissing(
+ int idx, Map<String, DecimalFormat.Unit[]> units) {
+ DecimalFormat.Unit otherVariantValue = units.get(OTHER)[idx];
+ for (DecimalFormat.Unit[] byBase : units.values()) {
+ if (byBase[idx] == null) {
+ byBase[idx] = otherVariantValue;
+ }
+ }
+ }
+
+ private static void copyFromPreviousIndex(int idx, Map<String, DecimalFormat.Unit[]> units) {
+ for (DecimalFormat.Unit[] byBase : units.values()) {
+ if (idx == 0) {
+ byBase[idx] = DecimalFormat.NULL_UNIT;
+ } else {
+ byBase[idx] = byBase[idx - 1];
+ }
+ }
+ }
+
+ private static boolean saveUnit(
+ DecimalFormat.Unit unit, String pluralVariant, int idx,
+ Map<String, DecimalFormat.Unit[]> units,
+ boolean overwrite) {
+ DecimalFormat.Unit[] byBase = units.get(pluralVariant);
+ if (byBase == null) {
+ byBase = new DecimalFormat.Unit[MAX_DIGITS];
+ units.put(pluralVariant, byBase);
+ }
+
+ // Don't overwrite a pre-existing value unless the "overwrite" flag is true.
+ if (!overwrite && byBase[idx] != null) {
+ return false;
+ }
+
+ // Save the value and return
+ byBase[idx] = unit;
+ return true;
+ }
+
+ /**
+ * Fetches a prefix or suffix given a plural variant and log10 value. If it
+ * can't find the given variant, it falls back to "other".
+ * @param prefixOrSuffix the prefix or suffix map
+ * @param variant the plural variant
+ * @param base log10 value. 0 <= base < MAX_DIGITS.
+ * @return the prefix or suffix.
+ */
+ static DecimalFormat.Unit getUnit(
+ Map<String, DecimalFormat.Unit[]> units, String variant, int base) {
+ DecimalFormat.Unit[] byBase = units.get(variant);
+ if (byBase == null) {
+ byBase = units.get(CompactDecimalDataCache.OTHER);
+ }
+ return byBase[base];
+ }
+}
import java.text.AttributedCharacterIterator;
import java.text.FieldPosition;
import java.text.ParsePosition;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.regex.Pattern;
-import com.ibm.icu.impl.number.FormatQuantity4;
-import com.ibm.icu.impl.number.Properties;
+import com.ibm.icu.text.CompactDecimalDataCache.Data;
+import com.ibm.icu.text.PluralRules.FixedDecimal;
+import com.ibm.icu.util.Currency;
+import com.ibm.icu.util.CurrencyAmount;
+import com.ibm.icu.util.Output;
import com.ibm.icu.util.ULocale;
/**
- * The CompactDecimalFormat produces abbreviated numbers, suitable for display in environments will
- * limited real estate. For example, 'Hits: 1.2B' instead of 'Hits: 1,200,000,000'. The format will
- * be appropriate for the given language, such as "1,2 Mrd." for German.
+ * The CompactDecimalFormat produces abbreviated numbers, suitable for display in environments will limited real estate.
+ * For example, 'Hits: 1.2B' instead of 'Hits: 1,200,000,000'. The format will be appropriate for the given language,
+ * such as "1,2 Mrd." for German.
+ * <p>
+ * For numbers under 1000 trillion (under 10^15, such as 123,456,789,012,345), the result will be short for supported
+ * languages. However, the result may sometimes exceed 7 characters, such as when there are combining marks or thin
+ * characters. In such cases, the visual width in fonts should still be short.
+ * <p>
+ * By default, there are 2 significant digits. After creation, if more than three significant digits are set (with
+ * setMaximumSignificantDigits), or if a fixed number of digits are set (with setMaximumIntegerDigits or
+ * setMaximumFractionDigits), then result may be wider.
+ * <p>
+ * The "short" style is also capable of formatting currency amounts, such as "$1.2M" instead of "$1,200,000.00" (English) or
+ * "5,3 Mio. €" instead of "5.300.000,00 €" (German). Localized data concerning longer formats is not available yet in
+ * the Unicode CLDR. Because of this, attempting to format a currency amount using the "long" style will produce
+ * an UnsupportedOperationException.
*
- * <p>For numbers under 1000 trillion (under 10^15, such as 123,456,789,012,345), the result will be
- * short for supported languages. However, the result may sometimes exceed 7 characters, such as
- * when there are combining marks or thin characters. In such cases, the visual width in fonts
- * should still be short.
- *
- * <p>By default, there are 2 significant digits. After creation, if more than three significant
- * digits are set (with setMaximumSignificantDigits), or if a fixed number of digits are set (with
- * setMaximumIntegerDigits or setMaximumFractionDigits), then result may be wider.
- *
- * <p>The "short" style is also capable of formatting currency amounts, such as "$1.2M" instead of
- * "$1,200,000.00" (English) or "5,3 Mio. €" instead of "5.300.000,00 €" (German). Localized data
- * concerning longer formats is not available yet in the Unicode CLDR. Because of this, attempting
- * to format a currency amount using the "long" style will produce an UnsupportedOperationException.
- *
- * <p>At this time, negative numbers and parsing are not supported, and will produce an
- * UnsupportedOperationException. Resetting the pattern prefixes or suffixes is not supported; the
- * method calls are ignored.
- *
- * <p>Note that important methods, like setting the number of decimals, will be moved up from
- * DecimalFormat to NumberFormat.
+ * At this time, negative numbers and parsing are not supported, and will produce an UnsupportedOperationException.
+ * Resetting the pattern prefixes or suffixes is not supported; the method calls are ignored.
+ * <p>
+ * Note that important methods, like setting the number of decimals, will be moved up from DecimalFormat to
+ * NumberFormat.
*
* @author markdavis
* @stable ICU 49
*/
public class CompactDecimalFormat extends DecimalFormat {
- /**
- * Style parameter for CompactDecimalFormat.
- *
- * @stable ICU 50
- */
- public enum CompactStyle {
+ private static final long serialVersionUID = 4716293295276629682L;
+
+// private static final int POSITIVE_PREFIX = 0, POSITIVE_SUFFIX = 1, AFFIX_SIZE = 2;
+ private static final CompactDecimalDataCache cache = new CompactDecimalDataCache();
+
+ private final Map<String, DecimalFormat.Unit[]> units;
+ private final Map<String, DecimalFormat.Unit[]> currencyUnits;
+ private final long[] divisor;
+ private final long[] currencyDivisor;
+ private final Map<String, Unit> pluralToCurrencyAffixes;
+ private CompactStyle style;
+
+ // null if created internally using explicit prefixes and suffixes.
+ private final PluralRules pluralRules;
+
+ /**
+ * Style parameter for CompactDecimalFormat.
+ * @stable ICU 50
+ */
+ public enum CompactStyle {
+ /**
+ * Short version, like "1.2T"
+ * @stable ICU 50
+ */
+ SHORT,
+ /**
+ * Longer version, like "1.2 trillion", if available. May return same result as SHORT if not.
+ * @stable ICU 50
+ */
+ LONG
+ }
+
+ /**
+ * Create a CompactDecimalFormat appropriate for a locale. The result may
+ * be affected by the number system in the locale, such as ar-u-nu-latn.
+ *
+ * @param locale the desired locale
+ * @param style the compact style
+ * @stable ICU 50
+ */
+ public static CompactDecimalFormat getInstance(ULocale locale, CompactStyle style) {
+ return new CompactDecimalFormat(locale, style);
+ }
+
/**
- * Short version, like "1.2T"
+ * Create a CompactDecimalFormat appropriate for a locale. The result may
+ * be affected by the number system in the locale, such as ar-u-nu-latn.
*
+ * @param locale the desired locale
+ * @param style the compact style
* @stable ICU 50
*/
- SHORT,
+ public static CompactDecimalFormat getInstance(Locale locale, CompactStyle style) {
+ return new CompactDecimalFormat(ULocale.forLocale(locale), style);
+ }
+
/**
- * Longer version, like "1.2 trillion", if available. May return same result as SHORT if not.
+ * The public mechanism is CompactDecimalFormat.getInstance().
*
+ * @param locale
+ * the desired locale
+ * @param style
+ * the compact style
+ */
+ CompactDecimalFormat(ULocale locale, CompactStyle style) {
+ this.pluralRules = PluralRules.forLocale(locale);
+ DecimalFormat format = (DecimalFormat) NumberFormat.getInstance(locale);
+ CompactDecimalDataCache.Data data = getData(locale, style);
+ CompactDecimalDataCache.Data currencyData = getCurrencyData(locale);
+ this.units = data.units;
+ this.divisor = data.divisors;
+ this.currencyUnits = currencyData.units;
+ this.currencyDivisor = currencyData.divisors;
+ this.style = style;
+ pluralToCurrencyAffixes = null;
+
+// DecimalFormat currencyFormat = (DecimalFormat) NumberFormat.getCurrencyInstance(locale);
+// // TODO fix to use plural-dependent affixes
+// Unit currency = new Unit(currencyFormat.getPositivePrefix(), currencyFormat.getPositiveSuffix());
+// pluralToCurrencyAffixes = new HashMap<String,Unit>();
+// for (String key : pluralRules.getKeywords()) {
+// pluralToCurrencyAffixes.put(key, currency);
+// }
+// // TODO fix to get right symbol for the count
+
+ finishInit(style, format.toPattern(), format.getDecimalFormatSymbols());
+ }
+
+ /**
+ * Create a short number "from scratch". Intended for internal use. The prefix, suffix, and divisor arrays are
+ * parallel, and provide the information for each power of 10. When formatting a value, the correct power of 10 is
+ * found, then the value is divided by the divisor, and the prefix and suffix are set (using
+ * setPositivePrefix/Suffix).
+ *
+ * @param pattern
+ * A number format pattern. Note that the prefix and suffix are discarded, and the decimals are
+ * overridden by default.
+ * @param formatSymbols
+ * Decimal format symbols, typically from a locale.
+ * @param style
+ * compact style.
+ * @param divisor
+ * An array of prefix values, one for each power of 10 from 0 to 14
+ * @param pluralAffixes
+ * A map from plural categories to affixes.
+ * @param currencyAffixes
+ * A map from plural categories to currency affixes.
+ * @param debugCreationErrors
+ * A collection of strings for debugging. If null on input, then any errors found will be added to that
+ * collection instead of throwing exceptions.
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
+ public CompactDecimalFormat(String pattern, DecimalFormatSymbols formatSymbols,
+ CompactStyle style, PluralRules pluralRules,
+ long[] divisor, Map<String,String[][]> pluralAffixes, Map<String, String[]> currencyAffixes,
+ Collection<String> debugCreationErrors) {
+
+ this.pluralRules = pluralRules;
+ this.units = otherPluralVariant(pluralAffixes, divisor, debugCreationErrors);
+ this.currencyUnits = otherPluralVariant(pluralAffixes, divisor, debugCreationErrors);
+ if (!pluralRules.getKeywords().equals(this.units.keySet())) {
+ debugCreationErrors.add("Missmatch in pluralCategories, should be: " + pluralRules.getKeywords() + ", was actually " + this.units.keySet());
+ }
+ this.divisor = divisor.clone();
+ this.currencyDivisor = divisor.clone();
+ if (currencyAffixes == null) {
+ pluralToCurrencyAffixes = null;
+ } else {
+ pluralToCurrencyAffixes = new HashMap<String,Unit>();
+ for (Entry<String, String[]> s : currencyAffixes.entrySet()) {
+ String[] pair = s.getValue();
+ pluralToCurrencyAffixes.put(s.getKey(), new Unit(pair[0], pair[1]));
+ }
+ }
+ finishInit(style, pattern, formatSymbols);
+ }
+
+ private void finishInit(CompactStyle style, String pattern, DecimalFormatSymbols formatSymbols) {
+ applyPattern(pattern);
+ setDecimalFormatSymbols(formatSymbols);
+ setMaximumSignificantDigits(2); // default significant digits
+ setSignificantDigitsUsed(true);
+ if (style == CompactStyle.SHORT) {
+ setGroupingUsed(false);
+ }
+ setCurrency(null);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @stable ICU 49
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null)
+ return false;
+ if (!super.equals(obj))
+ return false; // super does class check
+ CompactDecimalFormat other = (CompactDecimalFormat) obj;
+ return mapsAreEqual(units, other.units)
+ && Arrays.equals(divisor, other.divisor)
+ && (pluralToCurrencyAffixes == other.pluralToCurrencyAffixes
+ || pluralToCurrencyAffixes != null && pluralToCurrencyAffixes.equals(other.pluralToCurrencyAffixes))
+ && pluralRules.equals(other.pluralRules);
+ }
+
+ private boolean mapsAreEqual(
+ Map<String, DecimalFormat.Unit[]> lhs, Map<String, DecimalFormat.Unit[]> rhs) {
+ if (lhs.size() != rhs.size()) {
+ return false;
+ }
+ // For each MapEntry in lhs, see if there is a matching one in rhs.
+ for (Map.Entry<String, DecimalFormat.Unit[]> entry : lhs.entrySet()) {
+ DecimalFormat.Unit[] value = rhs.get(entry.getKey());
+ if (value == null || !Arrays.equals(entry.getValue(), value)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @stable ICU 49
+ */
+ @Override
+ public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) {
+ return format(number, null, toAppendTo, pos);
+ }
+
+ /**
+ * {@inheritDoc}
* @stable ICU 50
*/
- LONG
- }
-
- /**
- * Create a CompactDecimalFormat appropriate for a locale. The result may be affected by the
- * number system in the locale, such as ar-u-nu-latn.
- *
- * @param locale the desired locale
- * @param style the compact style
- * @stable ICU 50
- */
- public static CompactDecimalFormat getInstance(ULocale locale, CompactStyle style) {
- return new CompactDecimalFormat(locale, style);
- }
-
- /**
- * Create a CompactDecimalFormat appropriate for a locale. The result may be affected by the
- * number system in the locale, such as ar-u-nu-latn.
- *
- * @param locale the desired locale
- * @param style the compact style
- * @stable ICU 50
- */
- public static CompactDecimalFormat getInstance(Locale locale, CompactStyle style) {
- return new CompactDecimalFormat(ULocale.forLocale(locale), style);
- }
-
- /**
- * The public mechanism is CompactDecimalFormat.getInstance().
- *
- * @param locale the desired locale
- * @param style the compact style
- */
- CompactDecimalFormat(ULocale locale, CompactStyle style) {
- // Use the locale's default pattern
- String pattern = getPattern(locale, 0);
- symbols = DecimalFormatSymbols.getInstance(locale);
- properties = new Properties();
- properties.setCompactStyle(style);
- exportedProperties = new Properties();
- setPropertiesFromPattern(pattern, true);
- if (style == CompactStyle.SHORT) {
- // TODO: This was setGroupingUsed(false) in ICU 58. Is it okay that I changed it for ICU 59?
- properties.setMinimumGroupingDigits(2);
- }
- refreshFormatter();
- }
-
- /**
- * {@inheritDoc}
- *
- * @stable ICU 49
- */
- @Override
- public boolean equals(Object obj) {
- return super.equals(obj);
- }
-
- /**
- * {@inheritDoc}
- *
- * @stable ICU 49
- */
- @Override
- public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) {
- FormatQuantity4 fq = new FormatQuantity4(number);
- formatter.format(fq, toAppendTo, pos);
- fq.populateUFieldPosition(pos);
- return toAppendTo;
- }
-
- /**
- * {@inheritDoc}
- *
- * @stable ICU 50
- */
- @Override
- public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
- if (!(obj instanceof Number)) throw new IllegalArgumentException();
- Number number = (Number) obj;
- FormatQuantity4 fq = new FormatQuantity4(number);
- AttributedCharacterIterator result = formatter.formatToCharacterIterator(fq);
- return result;
- }
-
- /**
- * {@inheritDoc}
- *
- * @stable ICU 49
- */
- @Override
- public StringBuffer format(long number, StringBuffer toAppendTo, FieldPosition pos) {
- FormatQuantity4 fq = new FormatQuantity4(number);
- formatter.format(fq, toAppendTo, pos);
- fq.populateUFieldPosition(pos);
- return toAppendTo;
- }
-
- /**
- * {@inheritDoc}
- *
- * @stable ICU 49
- */
- @Override
- public StringBuffer format(BigInteger number, StringBuffer toAppendTo, FieldPosition pos) {
- FormatQuantity4 fq = new FormatQuantity4(number);
- formatter.format(fq, toAppendTo, pos);
- fq.populateUFieldPosition(pos);
- return toAppendTo;
- }
-
- /**
- * {@inheritDoc}
- *
- * @stable ICU 49
- */
- @Override
- public StringBuffer format(BigDecimal number, StringBuffer toAppendTo, FieldPosition pos) {
- FormatQuantity4 fq = new FormatQuantity4(number);
- formatter.format(fq, toAppendTo, pos);
- fq.populateUFieldPosition(pos);
- return toAppendTo;
- }
-
- /**
- * {@inheritDoc}
- *
- * @stable ICU 49
- */
- @Override
- public StringBuffer format(
- com.ibm.icu.math.BigDecimal number, StringBuffer toAppendTo, FieldPosition pos) {
- FormatQuantity4 fq = new FormatQuantity4(number.toBigDecimal());
- formatter.format(fq, toAppendTo, pos);
- fq.populateUFieldPosition(pos);
- return toAppendTo;
- }
-
-// /**
-// * {@inheritDoc}
-// *
-// * @internal ICU 57 technology preview
-// * @deprecated This API might change or be removed in a future release.
-// */
-// @Override
-// @Deprecated
-// public StringBuffer format(CurrencyAmount currAmt, StringBuffer toAppendTo, FieldPosition pos) {
-// // TODO(sffc)
-// throw new UnsupportedOperationException();
-// }
-
- /**
- * Parsing is currently unsupported, and throws an UnsupportedOperationException.
- *
- * @stable ICU 49
- */
- @Override
- public Number parse(String text, ParsePosition parsePosition) {
- throw new UnsupportedOperationException();
- }
-
- // DISALLOW Serialization, at least while draft
-
- private void writeObject(ObjectOutputStream out) throws IOException {
- throw new NotSerializableException();
- }
-
- private void readObject(ObjectInputStream in) throws IOException {
- throw new NotSerializableException();
- }
+ @Override
+ public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
+ if (!(obj instanceof Number)) {
+ throw new IllegalArgumentException();
+ }
+ Number number = (Number) obj;
+ Amount amount = toAmount(number.doubleValue(), null, null);
+ return super.formatToCharacterIterator(amount.getQty(), amount.getUnit());
+ }
+
+ /**
+ * {@inheritDoc}
+ * @stable ICU 49
+ */
+ @Override
+ public StringBuffer format(long number, StringBuffer toAppendTo, FieldPosition pos) {
+ return format((double) number, toAppendTo, pos);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @stable ICU 49
+ */
+ @Override
+ public StringBuffer format(BigInteger number, StringBuffer toAppendTo, FieldPosition pos) {
+ return format(number.doubleValue(), toAppendTo, pos);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @stable ICU 49
+ */
+ @Override
+ public StringBuffer format(BigDecimal number, StringBuffer toAppendTo, FieldPosition pos) {
+ return format(number.doubleValue(), toAppendTo, pos);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @stable ICU 49
+ */
+ @Override
+ public StringBuffer format(com.ibm.icu.math.BigDecimal number, StringBuffer toAppendTo, FieldPosition pos) {
+ return format(number.doubleValue(), toAppendTo, pos);
+ }
+ /**
+ * {@inheritDoc}
+ * @internal ICU 57 technology preview
+ * @deprecated This API might change or be removed in a future release.
+ */
+ @Override
+ @Deprecated
+ public StringBuffer format(CurrencyAmount currAmt, StringBuffer toAppendTo, FieldPosition pos) {
+ return format(currAmt.getNumber().doubleValue(), currAmt.getCurrency(), toAppendTo, pos);
+ }
+
+ /**
+ * Parsing is currently unsupported, and throws an UnsupportedOperationException.
+ * @stable ICU 49
+ */
+ @Override
+ public Number parse(String text, ParsePosition parsePosition) {
+ throw new UnsupportedOperationException();
+ }
+
+ // DISALLOW Serialization, at least while draft
+
+ private void writeObject(ObjectOutputStream out) throws IOException {
+ throw new NotSerializableException();
+ }
+
+ private void readObject(ObjectInputStream in) throws IOException {
+ throw new NotSerializableException();
+ }
+
+ /* INTERNALS */
+ private StringBuffer format(double number, Currency curr, StringBuffer toAppendTo, FieldPosition pos) {
+ if (curr != null && style == CompactStyle.LONG) {
+ throw new UnsupportedOperationException("CompactDecimalFormat does not support LONG style for currency.");
+ }
+
+ // Compute the scaled amount, prefix, and suffix appropriate for the number's magnitude.
+ Output<Unit> currencyUnit = new Output<Unit>();
+ Amount amount = toAmount(number, curr, currencyUnit);
+ Unit unit = amount.getUnit();
+
+ // Note that currencyUnit is a remnant. In almost all cases, it will be null.
+ StringBuffer prefix = new StringBuffer();
+ StringBuffer suffix = new StringBuffer();
+ if (currencyUnit.value != null) {
+ currencyUnit.value.writePrefix(prefix);
+ }
+ unit.writePrefix(prefix);
+ unit.writeSuffix(suffix);
+ if (currencyUnit.value != null) {
+ currencyUnit.value.writeSuffix(suffix);
+ }
+
+ if (curr == null) {
+ // Prevent locking when not formatting a currency number.
+ toAppendTo.append(escape(prefix.toString()));
+ super.format(amount.getQty(), toAppendTo, pos);
+ toAppendTo.append(escape(suffix.toString()));
+
+ } else {
+ // To perform the formatting, we set this DecimalFormat's pattern to have the correct prefix, suffix,
+ // and currency, and then reset it back to what it was before.
+ // This has to be synchronized since this information is held in the state of the DecimalFormat object.
+ synchronized(this) {
+
+ String originalPattern = this.toPattern();
+ Currency originalCurrency = this.getCurrency();
+ StringBuffer newPattern = new StringBuffer();
+
+ // Write prefixes and suffixes to the pattern. Note that we have to apply it to both halves of a
+ // positive/negative format (separated by ';')
+ int semicolonPos = originalPattern.indexOf(';');
+ newPattern.append(prefix);
+ if (semicolonPos != -1) {
+ newPattern.append(originalPattern, 0, semicolonPos);
+ newPattern.append(suffix);
+ newPattern.append(';');
+ newPattern.append(prefix);
+ }
+ newPattern.append(originalPattern, semicolonPos + 1, originalPattern.length());
+ newPattern.append(suffix);
+
+ // Overwrite the pattern and currency.
+ setCurrency(curr);
+ applyPattern(newPattern.toString());
+
+ // Actually perform the formatting.
+ super.format(amount.getQty(), toAppendTo, pos);
+
+ // Reset the pattern and currency.
+ setCurrency(originalCurrency);
+ applyPattern(originalPattern);
+ }
+ }
+ return toAppendTo;
+ }
+
+ private static final Pattern UNESCAPE_QUOTE = Pattern.compile("((?<!'))'");
+
+ private static String escape(String string) {
+ if (string.indexOf('\'') >= 0) {
+ return UNESCAPE_QUOTE.matcher(string).replaceAll("$1");
+ }
+ return string;
+ }
+
+ private Amount toAmount(double number, Currency curr, Output<Unit> currencyUnit) {
+ // We do this here so that the prefix or suffix we choose is always consistent
+ // with the rounding we do. This way, 999999 -> 1M instead of 1000K.
+ boolean negative = isNumberNegative(number);
+ number = adjustNumberAsInFormatting(number);
+ int base = number <= 1.0d ? 0 : (int) Math.log10(number);
+ if (base >= CompactDecimalDataCache.MAX_DIGITS) {
+ base = CompactDecimalDataCache.MAX_DIGITS - 1;
+ }
+ if (curr != null) {
+ number /= currencyDivisor[base];
+ } else {
+ number /= divisor[base];
+ }
+ String pluralVariant = getPluralForm(getFixedDecimal(number, toDigitList(number)));
+ if (pluralToCurrencyAffixes != null && currencyUnit != null) {
+ currencyUnit.value = pluralToCurrencyAffixes.get(pluralVariant);
+ }
+ if (negative) {
+ number = -number;
+ }
+ if ( curr != null ) {
+ return new Amount(number, CompactDecimalDataCache.getUnit(currencyUnits, pluralVariant, base));
+ } else {
+ return new Amount(number, CompactDecimalDataCache.getUnit(units, pluralVariant, base));
+ }
+ }
+
+ private void recordError(Collection<String> creationErrors, String errorMessage) {
+ if (creationErrors == null) {
+ throw new IllegalArgumentException(errorMessage);
+ }
+ creationErrors.add(errorMessage);
+ }
+
+ /**
+ * Manufacture the unit list from arrays
+ */
+ private Map<String, DecimalFormat.Unit[]> otherPluralVariant(Map<String, String[][]> pluralCategoryToPower10ToAffix,
+ long[] divisor, Collection<String> debugCreationErrors) {
+
+ // check for bad divisors
+ if (divisor.length < CompactDecimalDataCache.MAX_DIGITS) {
+ recordError(debugCreationErrors, "Must have at least " + CompactDecimalDataCache.MAX_DIGITS + " prefix items.");
+ }
+ long oldDivisor = 0;
+ for (int i = 0; i < divisor.length; ++i) {
+
+ // divisor must be a power of 10, and must be less than or equal to 10^i
+ int log = (int) Math.log10(divisor[i]);
+ if (log > i) {
+ recordError(debugCreationErrors, "Divisor[" + i + "] must be less than or equal to 10^" + i
+ + ", but is: " + divisor[i]);
+ }
+ long roundTrip = (long) Math.pow(10.0d, log);
+ if (roundTrip != divisor[i]) {
+ recordError(debugCreationErrors, "Divisor[" + i + "] must be a power of 10, but is: " + divisor[i]);
+ }
+
+ if (divisor[i] < oldDivisor) {
+ recordError(debugCreationErrors, "Bad divisor, the divisor for 10E" + i + "(" + divisor[i]
+ + ") is less than the divisor for the divisor for 10E" + (i - 1) + "(" + oldDivisor + ")");
+ }
+ oldDivisor = divisor[i];
+ }
+
+ Map<String, DecimalFormat.Unit[]> result = new HashMap<String, DecimalFormat.Unit[]>();
+ Map<String,Integer> seen = new HashMap<String,Integer>();
+
+ String[][] defaultPower10ToAffix = pluralCategoryToPower10ToAffix.get("other");
+
+ for (Entry<String, String[][]> pluralCategoryAndPower10ToAffix : pluralCategoryToPower10ToAffix.entrySet()) {
+ String pluralCategory = pluralCategoryAndPower10ToAffix.getKey();
+ String[][] power10ToAffix = pluralCategoryAndPower10ToAffix.getValue();
+
+ // we can't have one of the arrays be of different length
+ if (power10ToAffix.length != divisor.length) {
+ recordError(debugCreationErrors, "Prefixes & suffixes must be present for all divisors " + pluralCategory);
+ }
+ DecimalFormat.Unit[] units = new DecimalFormat.Unit[power10ToAffix.length];
+ for (int i = 0; i < power10ToAffix.length; i++) {
+ String[] pair = power10ToAffix[i];
+ if (pair == null) {
+ pair = defaultPower10ToAffix[i];
+ }
+
+ // we can't have bad pair
+ if (pair.length != 2 || pair[0] == null || pair[1] == null) {
+ recordError(debugCreationErrors, "Prefix or suffix is null for " + pluralCategory + ", " + i + ", " + Arrays.asList(pair));
+ continue;
+ }
+
+ // we can't have two different indexes with the same display
+ int log = (int) Math.log10(divisor[i]);
+ String key = pair[0] + "\uFFFF" + pair[1] + "\uFFFF" + (i - log);
+ Integer old = seen.get(key);
+ if (old == null) {
+ seen.put(key, i);
+ } else if (old != i) {
+ recordError(debugCreationErrors, "Collision between values for " + i + " and " + old
+ + " for [prefix/suffix/index-log(divisor)" + key.replace('\uFFFF', ';'));
+ }
+
+ units[i] = new Unit(pair[0], pair[1]);
+ }
+ result.put(pluralCategory, units);
+ }
+ return result;
+ }
+
+ private String getPluralForm(FixedDecimal fixedDecimal) {
+ if (pluralRules == null) {
+ return CompactDecimalDataCache.OTHER;
+ }
+ return pluralRules.select(fixedDecimal);
+ }
+
+ /**
+ * Gets the data for a particular locale and style. If style is unrecognized,
+ * we just return data for CompactStyle.SHORT.
+ * @param locale The locale.
+ * @param style The style.
+ * @return The data which must not be modified.
+ */
+ private Data getData(ULocale locale, CompactStyle style) {
+ CompactDecimalDataCache.DataBundle bundle = cache.get(locale);
+ switch (style) {
+ case SHORT:
+ return bundle.shortData;
+ case LONG:
+ return bundle.longData;
+ default:
+ return bundle.shortData;
+ }
+ }
+ /**
+ * Gets the currency data for a particular locale.
+ * Currently only short currency format is supported, since that is
+ * the only form in CLDR.
+ * @param locale The locale.
+ * @return The data which must not be modified.
+ */
+ private Data getCurrencyData(ULocale locale) {
+ CompactDecimalDataCache.DataBundle bundle = cache.get(locale);
+ return bundle.shortCurrencyData;
+ }
+
+ private static class Amount {
+ private final double qty;
+ private final Unit unit;
+
+ public Amount(double qty, Unit unit) {
+ this.qty = qty;
+ this.unit = unit;
+ }
+
+ public double getQty() {
+ return qty;
+ }
+
+ public Unit getUnit() {
+ return unit;
+ }
+ }
}
}
/**
- * Set plural rules. These are initially set in the constructor based on the locale,
+ * Set plural rules. These are initially set in the constructor based on the locale,
* and usually do not need to be changed.
*
* @param ruleDescription new plural rule description
* Set currency plural patterns. These are initially set in the constructor based on the
* locale, and usually do not need to be changed.
*
- * The decimal digits part of the pattern cannot be specified via this method. All plural
- * forms will use the same decimal pattern as set in the constructor of DecimalFormat. For
- * example, you can't set "0.0" for plural "few" but "0.00" for plural "many".
- *
* @param pluralCount the plural count for which the currency pattern will
* be overridden.
* @param pattern the new currency plural pattern
*
* @stable ICU 4.2
*/
- @Override
public Object clone() {
try {
CurrencyPluralInfo other = (CurrencyPluralInfo) super.clone();
*
* @stable ICU 4.2
*/
- @Override
public boolean equals(Object a) {
if (a instanceof CurrencyPluralInfo) {
CurrencyPluralInfo other = (CurrencyPluralInfo)a;
}
return false;
}
-
+
/**
- * Override hashCode
- *
+ * Mock implementation of hashCode(). This implementation always returns a constant
+ * value. When Java assertion is enabled, this method triggers an assertion failure.
* @internal
* @deprecated This API is ICU internal only.
*/
- @Override
@Deprecated
public int hashCode() {
- return pluralCountToCurrencyUnitPattern.hashCode()
- ^ pluralRules.hashCode()
- ^ ulocale.hashCode();
+ assert false : "hashCode not designed";
+ return 42;
}
/**
private void setupCurrencyPluralPattern(ULocale uloc) {
pluralCountToCurrencyUnitPattern = new HashMap<String, String>();
-
+
String numberStylePattern = NumberFormat.getPattern(uloc, NumberFormat.NUMBERSTYLE);
// Split the number style pattern into pos and neg if applicable
int separatorIndex = numberStylePattern.indexOf(";");
for (Map.Entry<String, String> e : map.entrySet()) {
String pluralCount = e.getKey();
String pattern = e.getValue();
-
+
// replace {0} with numberStylePattern
// and {1} with triple currency sign
String patternWithNumber = pattern.replace("{0}", numberStylePattern);
* Returns the array of strings used as digits, in order from 0 through 9
* Package private method - doesn't create a defensively copy.
* @return the array of digit strings
- * @internal
- * @deprecated This API is ICU internal only.
*/
- @Deprecated
- public String[] getDigitStringsLocal() {
+ String[] getDigitStringsLocal() {
return digitStrings;
}
setMonetaryGroupingSeparatorString(numberElements[11]);
setExponentMultiplicationSign(numberElements[12]);
- digit = '#'; // Localized pattern character no longer in CLDR
- padEscape = '*';
- sigDigit = '@';
+ digit = DecimalFormat.PATTERN_DIGIT; // Localized pattern character no longer in CLDR
+ padEscape = DecimalFormat.PATTERN_PAD_ESCAPE;
+ sigDigit = DecimalFormat.PATTERN_SIGNIFICANT_DIGIT;
CurrencyDisplayInfo info = CurrencyData.provider.getInstance(locale, true);
exponential = 'E';
}
if (serialVersionOnStream < 2) {
- padEscape = '*';
- plusSign = '+';
+ padEscape = DecimalFormat.PATTERN_PAD_ESCAPE;
+ plusSign = DecimalFormat.PATTERN_PLUS_SIGN;
exponentSeparator = String.valueOf(exponential);
// Although we read the exponential field on stream to create the
// exponentSeparator, we don't do the reverse, since scientific
groupingSeparatorString = String.valueOf(groupingSeparator);
}
if (percentString == null) {
- percentString = String.valueOf(percent);
+ percentString = String.valueOf(percentString);
}
if (perMillString == null) {
perMillString = String.valueOf(perMill);
+++ /dev/null
-// © 2016 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-/*
- *******************************************************************************
- * Copyright (C) 1996-2016, International Business Machines Corporation and
- * others. All Rights Reserved.
- *******************************************************************************
- */
-package com.ibm.icu.text;
-
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.math.BigInteger;
-import java.text.AttributedCharacterIterator;
-import java.text.AttributedString;
-import java.text.ChoiceFormat;
-import java.text.FieldPosition;
-import java.text.Format;
-import java.text.ParsePosition;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
-
-import com.ibm.icu.impl.ICUConfig;
-import com.ibm.icu.impl.PatternProps;
-import com.ibm.icu.impl.Utility;
-import com.ibm.icu.lang.UCharacter;
-import com.ibm.icu.math.BigDecimal;
-import com.ibm.icu.math.MathContext;
-import com.ibm.icu.text.PluralRules.FixedDecimal;
-import com.ibm.icu.util.Currency;
-import com.ibm.icu.util.Currency.CurrencyUsage;
-import com.ibm.icu.util.CurrencyAmount;
-import com.ibm.icu.util.ULocale;
-import com.ibm.icu.util.ULocale.Category;
-
-/**
- * {@icuenhanced java.text.DecimalFormat}.{@icu _usage_}
- *
- * <code>DecimalFormat</code> is a concrete subclass of {@link NumberFormat} that formats
- * decimal numbers. It has a variety of features designed to make it possible to parse and
- * format numbers in any locale, including support for Western, Arabic, or Indic digits.
- * It also supports different flavors of numbers, including integers ("123"), fixed-point
- * numbers ("123.4"), scientific notation ("1.23E4"), percentages ("12%"), and currency
- * amounts ("$123.00", "USD123.00", "123.00 US dollars"). All of these flavors can be
- * easily localized.
- *
- * <p>To obtain a {@link NumberFormat} for a specific locale (including the default
- * locale) call one of <code>NumberFormat</code>'s factory methods such as {@link
- * NumberFormat#getInstance}. Do not call the <code>DecimalFormat</code> constructors
- * directly, unless you know what you are doing, since the {@link NumberFormat} factory
- * methods may return subclasses other than <code>DecimalFormat</code>. If you need to
- * customize the format object, do something like this:
- *
- * <blockquote><pre>
- * NumberFormat f = NumberFormat.getInstance(loc);
- * if (f instanceof DecimalFormat) {
- * ((DecimalFormat) f).setDecimalSeparatorAlwaysShown(true);
- * }</pre></blockquote>
- *
- * <p><strong>Example Usage</strong>
- *
- * Print out a number using the localized number, currency, and percent
- * format for each locale.
- *
- * <blockquote><pre>
- * Locale[] locales = NumberFormat.getAvailableLocales();
- * double myNumber = -1234.56;
- * NumberFormat format;
- * for (int j=0; j<3; ++j) {
- * System.out.println("FORMAT");
- * for (int i = 0; i < locales.length; ++i) {
- * if (locales[i].getCountry().length() == 0) {
- * // Skip language-only locales
- * continue;
- * }
- * System.out.print(locales[i].getDisplayName());
- * switch (j) {
- * case 0:
- * format = NumberFormat.getInstance(locales[i]); break;
- * case 1:
- * format = NumberFormat.getCurrencyInstance(locales[i]); break;
- * default:
- * format = NumberFormat.getPercentInstance(locales[i]); break;
- * }
- * try {
- * // Assume format is a DecimalFormat
- * System.out.print(": " + ((DecimalFormat) format).toPattern()
- * + " -> " + form.format(myNumber));
- * } catch (Exception e) {}
- * try {
- * System.out.println(" -> " + format.parse(form.format(myNumber)));
- * } catch (ParseException e) {}
- * }
- * }</pre></blockquote>
- *
- * <p>Another example use getInstance(style).<br>
- * Print out a number using the localized number, currency, percent,
- * scientific, integer, iso currency, and plural currency format for each locale.
- *
- * <blockquote><pre>
- * ULocale locale = new ULocale("en_US");
- * double myNumber = 1234.56;
- * for (int j=NumberFormat.NUMBERSTYLE; j<=NumberFormat.PLURALCURRENCYSTYLE; ++j) {
- * NumberFormat format = NumberFormat.getInstance(locale, j);
- * try {
- * // Assume format is a DecimalFormat
- * System.out.print(": " + ((DecimalFormat) format).toPattern()
- * + " -> " + form.format(myNumber));
- * } catch (Exception e) {}
- * try {
- * System.out.println(" -> " + format.parse(form.format(myNumber)));
- * } catch (ParseException e) {}
- * }</pre></blockquote>
- *
- * <h3>Patterns</h3>
- *
- * <p>A <code>DecimalFormat</code> consists of a <em>pattern</em> and a set of
- * <em>symbols</em>. The pattern may be set directly using {@link #applyPattern}, or
- * indirectly using other API methods which manipulate aspects of the pattern, such as the
- * minimum number of integer digits. The symbols are stored in a {@link
- * DecimalFormatSymbols} object. When using the {@link NumberFormat} factory methods, the
- * pattern and symbols are read from ICU's locale data.
- *
- * <h4>Special Pattern Characters</h4>
- *
- * <p>Many characters in a pattern are taken literally; they are matched during parsing
- * and output unchanged during formatting. Special characters, on the other hand, stand
- * for other characters, strings, or classes of characters. For example, the '#'
- * character is replaced by a localized digit. Often the replacement character is the
- * same as the pattern character; in the U.S. locale, the ',' grouping character is
- * replaced by ','. However, the replacement is still happening, and if the symbols are
- * modified, the grouping character changes. Some special characters affect the behavior
- * of the formatter by their presence; for example, if the percent character is seen, then
- * the value is multiplied by 100 before being displayed.
- *
- * <p>To insert a special character in a pattern as a literal, that is, without any
- * special meaning, the character must be quoted. There are some exceptions to this which
- * are noted below.
- *
- * <p>The characters listed here are used in non-localized patterns. Localized patterns
- * use the corresponding characters taken from this formatter's {@link
- * DecimalFormatSymbols} object instead, and these characters lose their special status.
- * Two exceptions are the currency sign and quote, which are not localized.
- *
- * <blockquote>
- * <table border=0 cellspacing=3 cellpadding=0 summary="Chart showing symbol,
- * location, localized, and meaning.">
- * <tr style="background-color: #ccccff">
- * <th align=left>Symbol
- * <th align=left>Location
- * <th align=left>Localized?
- * <th align=left>Meaning
- * <tr style="vertical-align: top;">
- * <td><code>0</code>
- * <td>Number
- * <td>Yes
- * <td>Digit
- * <tr style="vertical-align: top; background-color: #eeeeff;">
- * <td><code>1-9</code>
- * <td>Number
- * <td>Yes
- * <td>'1' through '9' indicate rounding.
- * <tr style="vertical-align: top;">
- * <td><code>@</code>
- * <td>Number
- * <td>No
- * <td>Significant digit
- * <tr style="vertical-align: top; background-color: #eeeeff;">
- * <td><code>#</code>
- * <td>Number
- * <td>Yes
- * <td>Digit, zero shows as absent
- * <tr style="vertical-align: top;">
- * <td><code>.</code>
- * <td>Number
- * <td>Yes
- * <td>Decimal separator or monetary decimal separator
- * <tr style="vertical-align: top; background-color: #eeeeff;">
- * <td><code>-</code>
- * <td>Number
- * <td>Yes
- * <td>Minus sign
- * <tr style="vertical-align: top;">
- * <td><code>,</code>
- * <td>Number
- * <td>Yes
- * <td>Grouping separator
- * <tr style="vertical-align: top; background-color: #eeeeff;">
- * <td><code>E</code>
- * <td>Number
- * <td>Yes
- * <td>Separates mantissa and exponent in scientific notation.
- * <em>Need not be quoted in prefix or suffix.</em>
- * <tr style="vertical-align: top;">
- * <td><code>+</code>
- * <td>Exponent
- * <td>Yes
- * <td>Prefix positive exponents with localized plus sign.
- * <em>Need not be quoted in prefix or suffix.</em>
- * <tr style="vertical-align: top; background-color: #eeeeff;">
- * <td><code>;</code>
- * <td>Subpattern boundary
- * <td>Yes
- * <td>Separates positive and negative subpatterns
- * <tr style="vertical-align: top;">
- * <td><code>%</code>
- * <td>Prefix or suffix
- * <td>Yes
- * <td>Multiply by 100 and show as percentage
- * <tr style="vertical-align: top; background-color: #eeeeff;">
- * <td><code>\u2030</code>
- * <td>Prefix or suffix
- * <td>Yes
- * <td>Multiply by 1000 and show as per mille
- * <tr style="vertical-align: top;">
- * <td><code>¤</code> (<code>\u00A4</code>)
- * <td>Prefix or suffix
- * <td>No
- * <td>Currency sign, replaced by currency symbol. If
- * doubled, replaced by international currency symbol.
- * If tripled, replaced by currency plural names, for example,
- * "US dollar" or "US dollars" for America.
- * If present in a pattern, the monetary decimal separator
- * is used instead of the decimal separator.
- * <tr style="vertical-align: top; background-color: #eeeeff;">
- * <td><code>'</code>
- * <td>Prefix or suffix
- * <td>No
- * <td>Used to quote special characters in a prefix or suffix,
- * for example, <code>"'#'#"</code> formats 123 to
- * <code>"#123"</code>. To create a single quote
- * itself, use two in a row: <code>"# o''clock"</code>.
- * <tr style="vertical-align: top;">
- * <td><code>*</code>
- * <td>Prefix or suffix boundary
- * <td>Yes
- * <td>Pad escape, precedes pad character
- * </table>
- * </blockquote>
- *
- * <p>A <code>DecimalFormat</code> pattern contains a postive and negative subpattern, for
- * example, "#,##0.00;(#,##0.00)". Each subpattern has a prefix, a numeric part, and a
- * suffix. If there is no explicit negative subpattern, the negative subpattern is the
- * localized minus sign prefixed to the positive subpattern. That is, "0.00" alone is
- * equivalent to "0.00;-0.00". If there is an explicit negative subpattern, it serves
- * only to specify the negative prefix and suffix; the number of digits, minimal digits,
- * and other characteristics are ignored in the negative subpattern. That means that
- * "#,##0.0#;(#)" has precisely the same result as "#,##0.0#;(#,##0.0#)".
- *
- * <p>The prefixes, suffixes, and various symbols used for infinity, digits, thousands
- * separators, decimal separators, etc. may be set to arbitrary values, and they will
- * appear properly during formatting. However, care must be taken that the symbols and
- * strings do not conflict, or parsing will be unreliable. For example, either the
- * positive and negative prefixes or the suffixes must be distinct for {@link #parse} to
- * be able to distinguish positive from negative values. Another example is that the
- * decimal separator and thousands separator should be distinct characters, or parsing
- * will be impossible.
- *
- * <p>The <em>grouping separator</em> is a character that separates clusters of integer
- * digits to make large numbers more legible. It commonly used for thousands, but in some
- * locales it separates ten-thousands. The <em>grouping size</em> is the number of digits
- * between the grouping separators, such as 3 for "100,000,000" or 4 for "1 0000
- * 0000". There are actually two different grouping sizes: One used for the least
- * significant integer digits, the <em>primary grouping size</em>, and one used for all
- * others, the <em>secondary grouping size</em>. In most locales these are the same, but
- * sometimes they are different. For example, if the primary grouping interval is 3, and
- * the secondary is 2, then this corresponds to the pattern "#,##,##0", and the number
- * 123456789 is formatted as "12,34,56,789". If a pattern contains multiple grouping
- * separators, the interval between the last one and the end of the integer defines the
- * primary grouping size, and the interval between the last two defines the secondary
- * grouping size. All others are ignored, so "#,##,###,####" == "###,###,####" ==
- * "##,#,###,####".
- *
- * <p>Illegal patterns, such as "#.#.#" or "#.###,###", will cause
- * <code>DecimalFormat</code> to throw an {@link IllegalArgumentException} with a message
- * that describes the problem.
- *
- * <h4>Pattern BNF</h4>
- *
- * <pre>
- * pattern := subpattern (';' subpattern)?
- * subpattern := prefix? number exponent? suffix?
- * number := (integer ('.' fraction)?) | sigDigits
- * prefix := '\u0000'..'\uFFFD' - specialCharacters
- * suffix := '\u0000'..'\uFFFD' - specialCharacters
- * integer := '#'* '0'* '0'
- * fraction := '0'* '#'*
- * sigDigits := '#'* '@' '@'* '#'*
- * exponent := 'E' '+'? '0'* '0'
- * padSpec := '*' padChar
- * padChar := '\u0000'..'\uFFFD' - quote
- *  
- * Notation:
- * X* 0 or more instances of X
- * X? 0 or 1 instances of X
- * X|Y either X or Y
- * C..D any character from C up to D, inclusive
- * S-T characters in S, except those in T
- * </pre>
- * The first subpattern is for positive numbers. The second (optional)
- * subpattern is for negative numbers.
- *
- * <p>Not indicated in the BNF syntax above:
- *
- * <ul>
- *
- * <li>The grouping separator ',' can occur inside the integer and sigDigits
- * elements, between any two pattern characters of that element, as long as the integer or
- * sigDigits element is not followed by the exponent element.
- *
- * <li>Two grouping intervals are recognized: That between the decimal point and the first
- * grouping symbol, and that between the first and second grouping symbols. These
- * intervals are identical in most locales, but in some locales they differ. For example,
- * the pattern "#,##,###" formats the number 123456789 as
- * "12,34,56,789".
- *
- * <li>The pad specifier <code>padSpec</code> may appear before the prefix, after the
- * prefix, before the suffix, after the suffix, or not at all.
- *
- * <li>In place of '0', the digits '1' through '9' may be used to indicate a rounding
- * increment.
- *
- * </ul>
- *
- * <h4>Parsing</h4>
- *
- * <p><code>DecimalFormat</code> parses all Unicode characters that represent decimal
- * digits, as defined by {@link UCharacter#digit}. In addition,
- * <code>DecimalFormat</code> also recognizes as digits the ten consecutive characters
- * starting with the localized zero digit defined in the {@link DecimalFormatSymbols}
- * object. During formatting, the {@link DecimalFormatSymbols}-based digits are output.
- *
- * <p>During parsing, grouping separators are ignored.
- *
- * <p>For currency parsing, the formatter is able to parse every currency style formats no
- * matter which style the formatter is constructed with. For example, a formatter
- * instance gotten from NumberFormat.getInstance(ULocale, NumberFormat.CURRENCYSTYLE) can
- * parse formats such as "USD1.00" and "3.00 US dollars".
- *
- * <p>If {@link #parse(String, ParsePosition)} fails to parse a string, it returns
- * <code>null</code> and leaves the parse position unchanged. The convenience method
- * {@link #parse(String)} indicates parse failure by throwing a {@link
- * java.text.ParseException}.
- *
- * <p>Parsing an extremely large or small absolute value (such as 1.0E10000 or 1.0E-10000)
- * requires huge memory allocation for representing the parsed number. Such input may expose
- * a risk of DoS attacks. To prevent huge memory allocation triggered by such inputs,
- * <code>DecimalFormat</code> internally limits of maximum decimal digits to be 1000. Thus,
- * an input string resulting more than 1000 digits in plain decimal representation (non-exponent)
- * will be treated as either overflow (positive/negative infinite) or underflow (+0.0/-0.0).
- *
- * <h4>Formatting</h4>
- *
- * <p>Formatting is guided by several parameters, all of which can be specified either
- * using a pattern or using the API. The following description applies to formats that do
- * not use <a href="#sci">scientific notation</a> or <a href="#sigdig">significant
- * digits</a>.
- *
- * <ul><li>If the number of actual integer digits exceeds the <em>maximum integer
- * digits</em>, then only the least significant digits are shown. For example, 1997 is
- * formatted as "97" if the maximum integer digits is set to 2.
- *
- * <li>If the number of actual integer digits is less than the <em>minimum integer
- * digits</em>, then leading zeros are added. For example, 1997 is formatted as "01997"
- * if the minimum integer digits is set to 5.
- *
- * <li>If the number of actual fraction digits exceeds the <em>maximum fraction
- * digits</em>, then half-even rounding it performed to the maximum fraction digits. For
- * example, 0.125 is formatted as "0.12" if the maximum fraction digits is 2. This
- * behavior can be changed by specifying a rounding increment and a rounding mode.
- *
- * <li>If the number of actual fraction digits is less than the <em>minimum fraction
- * digits</em>, then trailing zeros are added. For example, 0.125 is formatted as
- * "0.1250" if the mimimum fraction digits is set to 4.
- *
- * <li>Trailing fractional zeros are not displayed if they occur <em>j</em> positions
- * after the decimal, where <em>j</em> is less than the maximum fraction digits. For
- * example, 0.10004 is formatted as "0.1" if the maximum fraction digits is four or less.
- * </ul>
- *
- * <p><strong>Special Values</strong>
- *
- * <p><code>NaN</code> is represented as a single character, typically
- * <code>\uFFFD</code>. This character is determined by the {@link
- * DecimalFormatSymbols} object. This is the only value for which the prefixes and
- * suffixes are not used.
- *
- * <p>Infinity is represented as a single character, typically <code>\u221E</code>,
- * with the positive or negative prefixes and suffixes applied. The infinity character is
- * determined by the {@link DecimalFormatSymbols} object.
- *
- * <h4><a name="sci">Scientific Notation</a></h4>
- *
- * <p>Numbers in scientific notation are expressed as the product of a mantissa and a
- * power of ten, for example, 1234 can be expressed as 1.234 x 10<sup>3</sup>. The
- * mantissa is typically in the half-open interval [1.0, 10.0) or sometimes [0.0, 1.0),
- * but it need not be. <code>DecimalFormat</code> supports arbitrary mantissas.
- * <code>DecimalFormat</code> can be instructed to use scientific notation through the API
- * or through the pattern. In a pattern, the exponent character immediately followed by
- * one or more digit characters indicates scientific notation. Example: "0.###E0" formats
- * the number 1234 as "1.234E3".
- *
- * <ul>
- *
- * <li>The number of digit characters after the exponent character gives the minimum
- * exponent digit count. There is no maximum. Negative exponents are formatted using the
- * localized minus sign, <em>not</em> the prefix and suffix from the pattern. This allows
- * patterns such as "0.###E0 m/s". To prefix positive exponents with a localized plus
- * sign, specify '+' between the exponent and the digits: "0.###E+0" will produce formats
- * "1E+1", "1E+0", "1E-1", etc. (In localized patterns, use the localized plus sign
- * rather than '+'.)
- *
- * <li>The minimum number of integer digits is achieved by adjusting the exponent.
- * Example: 0.00123 formatted with "00.###E0" yields "12.3E-4". This only happens if
- * there is no maximum number of integer digits. If there is a maximum, then the minimum
- * number of integer digits is fixed at one.
- *
- * <li>The maximum number of integer digits, if present, specifies the exponent grouping.
- * The most common use of this is to generate <em>engineering notation</em>, in which the
- * exponent is a multiple of three, e.g., "##0.###E0". The number 12345 is formatted
- * using "##0.####E0" as "12.345E3".
- *
- * <li>When using scientific notation, the formatter controls the digit counts using
- * significant digits logic. The maximum number of significant digits limits the total
- * number of integer and fraction digits that will be shown in the mantissa; it does not
- * affect parsing. For example, 12345 formatted with "##0.##E0" is "12.3E3". See the
- * section on significant digits for more details.
- *
- * <li>The number of significant digits shown is determined as follows: If
- * areSignificantDigitsUsed() returns false, then the minimum number of significant digits
- * shown is one, and the maximum number of significant digits shown is the sum of the
- * <em>minimum integer</em> and <em>maximum fraction</em> digits, and is unaffected by the
- * maximum integer digits. If this sum is zero, then all significant digits are shown.
- * If areSignificantDigitsUsed() returns true, then the significant digit counts are
- * specified by getMinimumSignificantDigits() and getMaximumSignificantDigits(). In this
- * case, the number of integer digits is fixed at one, and there is no exponent grouping.
- *
- * <li>Exponential patterns may not contain grouping separators.
- *
- * </ul>
- *
- * <h4><a name="sigdig">Significant Digits</a></h4>
- *
- * <code>DecimalFormat</code> has two ways of controlling how many digits are shows: (a)
- * significant digits counts, or (b) integer and fraction digit counts. Integer and
- * fraction digit counts are described above. When a formatter is using significant
- * digits counts, the number of integer and fraction digits is not specified directly, and
- * the formatter settings for these counts are ignored. Instead, the formatter uses
- * however many integer and fraction digits are required to display the specified number
- * of significant digits. Examples:
- *
- * <blockquote>
- * <table border=0 cellspacing=3 cellpadding=0>
- * <tr style="background-color: #ccccff">
- * <th align=left>Pattern
- * <th align=left>Minimum significant digits
- * <th align=left>Maximum significant digits
- * <th align=left>Number
- * <th align=left>Output of format()
- * <tr style="vertical-align: top;">
- * <td><code>@@@</code>
- * <td>3
- * <td>3
- * <td>12345
- * <td><code>12300</code>
- * <tr style="vertical-align: top; background-color: #eeeeff;">
- * <td><code>@@@</code>
- * <td>3
- * <td>3
- * <td>0.12345
- * <td><code>0.123</code>
- * <tr style="vertical-align: top;">
- * <td><code>@@##</code>
- * <td>2
- * <td>4
- * <td>3.14159
- * <td><code>3.142</code>
- * <tr style="vertical-align: top; background-color: #eeeeff;">
- * <td><code>@@##</code>
- * <td>2
- * <td>4
- * <td>1.23004
- * <td><code>1.23</code>
- * </table>
- * </blockquote>
- *
- * <ul>
- *
- * <li>Significant digit counts may be expressed using patterns that specify a minimum and
- * maximum number of significant digits. These are indicated by the <code>'@'</code> and
- * <code>'#'</code> characters. The minimum number of significant digits is the number of
- * <code>'@'</code> characters. The maximum number of significant digits is the number of
- * <code>'@'</code> characters plus the number of <code>'#'</code> characters following on
- * the right. For example, the pattern <code>"@@@"</code> indicates exactly 3 significant
- * digits. The pattern <code>"@##"</code> indicates from 1 to 3 significant digits.
- * Trailing zero digits to the right of the decimal separator are suppressed after the
- * minimum number of significant digits have been shown. For example, the pattern
- * <code>"@##"</code> formats the number 0.1203 as <code>"0.12"</code>.
- *
- * <li>If a pattern uses significant digits, it may not contain a decimal separator, nor
- * the <code>'0'</code> pattern character. Patterns such as <code>"@00"</code> or
- * <code>"@.###"</code> are disallowed.
- *
- * <li>Any number of <code>'#'</code> characters may be prepended to the left of the
- * leftmost <code>'@'</code> character. These have no effect on the minimum and maximum
- * significant digits counts, but may be used to position grouping separators. For
- * example, <code>"#,#@#"</code> indicates a minimum of one significant digits, a maximum
- * of two significant digits, and a grouping size of three.
- *
- * <li>In order to enable significant digits formatting, use a pattern containing the
- * <code>'@'</code> pattern character. Alternatively, call {@link
- * #setSignificantDigitsUsed setSignificantDigitsUsed(true)}.
- *
- * <li>In order to disable significant digits formatting, use a pattern that does not
- * contain the <code>'@'</code> pattern character. Alternatively, call {@link
- * #setSignificantDigitsUsed setSignificantDigitsUsed(false)}.
- *
- * <li>The number of significant digits has no effect on parsing.
- *
- * <li>Significant digits may be used together with exponential notation. Such patterns
- * are equivalent to a normal exponential pattern with a minimum and maximum integer digit
- * count of one, a minimum fraction digit count of <code>getMinimumSignificantDigits() -
- * 1</code>, and a maximum fraction digit count of <code>getMaximumSignificantDigits() -
- * 1</code>. For example, the pattern <code>"@@###E0"</code> is equivalent to
- * <code>"0.0###E0"</code>.
- *
- * <li>If signficant digits are in use, then the integer and fraction digit counts, as set
- * via the API, are ignored. If significant digits are not in use, then the signficant
- * digit counts, as set via the API, are ignored.
- *
- * </ul>
- *
- * <h4>Padding</h4>
- *
- * <p><code>DecimalFormat</code> supports padding the result of {@link #format} to a
- * specific width. Padding may be specified either through the API or through the pattern
- * syntax. In a pattern the pad escape character, followed by a single pad character,
- * causes padding to be parsed and formatted. The pad escape character is '*' in
- * unlocalized patterns, and can be localized using {@link
- * DecimalFormatSymbols#setPadEscape}. For example, <code>"$*x#,##0.00"</code> formats
- * 123 to <code>"$xx123.00"</code>, and 1234 to <code>"$1,234.00"</code>.
- *
- * <ul>
- *
- * <li>When padding is in effect, the width of the positive subpattern, including prefix
- * and suffix, determines the format width. For example, in the pattern <code>"* #0
- * o''clock"</code>, the format width is 10.
- *
- * <li>The width is counted in 16-bit code units (Java <code>char</code>s).
- *
- * <li>Some parameters which usually do not matter have meaning when padding is used,
- * because the pattern width is significant with padding. In the pattern "*
- * ##,##,#,##0.##", the format width is 14. The initial characters "##,##," do not affect
- * the grouping size or maximum integer digits, but they do affect the format width.
- *
- * <li>Padding may be inserted at one of four locations: before the prefix, after the
- * prefix, before the suffix, or after the suffix. If padding is specified in any other
- * location, {@link #applyPattern} throws an {@link IllegalArgumentException}. If there
- * is no prefix, before the prefix and after the prefix are equivalent, likewise for the
- * suffix.
- *
- * <li>When specified in a pattern, the 16-bit <code>char</code> immediately following the
- * pad escape is the pad character. This may be any character, including a special pattern
- * character. That is, the pad escape <em>escapes</em> the following character. If there
- * is no character after the pad escape, then the pattern is illegal.
- *
- * </ul>
- *
- * <p>
- * <strong>Rounding</strong>
- *
- * <p><code>DecimalFormat</code> supports rounding to a specific increment. For example,
- * 1230 rounded to the nearest 50 is 1250. 1.234 rounded to the nearest 0.65 is 1.3. The
- * rounding increment may be specified through the API or in a pattern. To specify a
- * rounding increment in a pattern, include the increment in the pattern itself. "#,#50"
- * specifies a rounding increment of 50. "#,##0.05" specifies a rounding increment of
- * 0.05.
- *
- * <ul>
- *
- * <li>Rounding only affects the string produced by formatting. It does not affect
- * parsing or change any numerical values.
- *
- * <li>A <em>rounding mode</em> determines how values are rounded; see the {@link
- * com.ibm.icu.math.BigDecimal} documentation for a description of the modes. Rounding
- * increments specified in patterns use the default mode, {@link
- * com.ibm.icu.math.BigDecimal#ROUND_HALF_EVEN}.
- *
- * <li>Some locales use rounding in their currency formats to reflect the smallest
- * currency denomination.
- *
- * <li>In a pattern, digits '1' through '9' specify rounding, but otherwise behave
- * identically to digit '0'.
- *
- * </ul>
- *
- * <h4>Synchronization</h4>
- *
- * <p><code>DecimalFormat</code> objects are not synchronized. Multiple threads should
- * not access one formatter concurrently.
- *
- * @see java.text.Format
- * @see NumberFormat
- * @author Mark Davis
- * @author Alan Liu
- * @deprecated DecimalFormat was overhauled in ICU 59. This is the old implementation, provided
- * temporarily to ease the transition. This class will be removed from ICU 60.
- */
-@Deprecated
-public class DecimalFormat_ICU58 extends NumberFormat {
-
- /**
- * Creates a DecimalFormat using the default pattern and symbols for the default
- * <code>FORMAT</code> locale. This is a convenient way to obtain a DecimalFormat when
- * internationalization is not the main concern.
- *
- * <p>To obtain standard formats for a given locale, use the factory methods on
- * NumberFormat such as getNumberInstance. These factories will return the most
- * appropriate sub-class of NumberFormat for a given locale.
- *
- * @see NumberFormat#getInstance
- * @see NumberFormat#getNumberInstance
- * @see NumberFormat#getCurrencyInstance
- * @see NumberFormat#getPercentInstance
- * @see Category#FORMAT
- * @stable ICU 2.0
- */
- public DecimalFormat_ICU58() {
- ULocale def = ULocale.getDefault(Category.FORMAT);
- String pattern = getPattern(def, 0);
- // Always applyPattern after the symbols are set
- this.symbols = new DecimalFormatSymbols(def);
- setCurrency(Currency.getInstance(def));
- applyPatternWithoutExpandAffix(pattern, false);
- if (currencySignCount == CURRENCY_SIGN_COUNT_IN_PLURAL_FORMAT) {
- currencyPluralInfo = new CurrencyPluralInfo(def);
- // the exact pattern is not known until the plural count is known.
- // so, no need to expand affix now.
- } else {
- expandAffixAdjustWidth(null);
- }
- }
-
- /**
- * Creates a DecimalFormat from the given pattern and the symbols for the default
- * <code>FORMAT</code> locale. This is a convenient way to obtain a DecimalFormat when
- * internationalization is not the main concern.
- *
- * <p>To obtain standard formats for a given locale, use the factory methods on
- * NumberFormat such as getNumberInstance. These factories will return the most
- * appropriate sub-class of NumberFormat for a given locale.
- *
- * @param pattern A non-localized pattern string.
- * @throws IllegalArgumentException if the given pattern is invalid.
- * @see NumberFormat#getInstance
- * @see NumberFormat#getNumberInstance
- * @see NumberFormat#getCurrencyInstance
- * @see NumberFormat#getPercentInstance
- * @see Category#FORMAT
- * @stable ICU 2.0
- */
- public DecimalFormat_ICU58(String pattern) {
- // Always applyPattern after the symbols are set
- ULocale def = ULocale.getDefault(Category.FORMAT);
- this.symbols = new DecimalFormatSymbols(def);
- setCurrency(Currency.getInstance(def));
- applyPatternWithoutExpandAffix(pattern, false);
- if (currencySignCount == CURRENCY_SIGN_COUNT_IN_PLURAL_FORMAT) {
- currencyPluralInfo = new CurrencyPluralInfo(def);
- } else {
- expandAffixAdjustWidth(null);
- }
- }
-
- /**
- * Creates a DecimalFormat from the given pattern and symbols. Use this constructor
- * when you need to completely customize the behavior of the format.
- *
- * <p>To obtain standard formats for a given locale, use the factory methods on
- * NumberFormat such as getInstance or getCurrencyInstance. If you need only minor
- * adjustments to a standard format, you can modify the format returned by a
- * NumberFormat factory method.
- *
- * @param pattern a non-localized pattern string
- * @param symbols the set of symbols to be used
- * @exception IllegalArgumentException if the given pattern is invalid
- * @see NumberFormat#getInstance
- * @see NumberFormat#getNumberInstance
- * @see NumberFormat#getCurrencyInstance
- * @see NumberFormat#getPercentInstance
- * @see DecimalFormatSymbols
- * @stable ICU 2.0
- */
- public DecimalFormat_ICU58(String pattern, DecimalFormatSymbols symbols) {
- createFromPatternAndSymbols(pattern, symbols);
- }
-
- private void createFromPatternAndSymbols(String pattern, DecimalFormatSymbols inputSymbols) {
- // Always applyPattern after the symbols are set
- symbols = (DecimalFormatSymbols) inputSymbols.clone();
- if (pattern.indexOf(CURRENCY_SIGN) >= 0) {
- // Only spend time with currency symbols when we're going to display it.
- // Also set some defaults before the apply pattern.
- setCurrencyForSymbols();
- }
- applyPatternWithoutExpandAffix(pattern, false);
- if (currencySignCount == CURRENCY_SIGN_COUNT_IN_PLURAL_FORMAT) {
- currencyPluralInfo = new CurrencyPluralInfo(symbols.getULocale());
- } else {
- expandAffixAdjustWidth(null);
- }
- }
-
- /**
- * Creates a DecimalFormat from the given pattern, symbols, information used for
- * currency plural format, and format style. Use this constructor when you need to
- * completely customize the behavior of the format.
- *
- * <p>To obtain standard formats for a given locale, use the factory methods on
- * NumberFormat such as getInstance or getCurrencyInstance.
- *
- * <p>If you need only minor adjustments to a standard format, you can modify the
- * format returned by a NumberFormat factory method using the setters.
- *
- * <p>If you want to completely customize a decimal format, using your own
- * DecimalFormatSymbols (such as group separators) and your own information for
- * currency plural formatting (such as plural rule and currency plural patterns), you
- * can use this constructor.
- *
- * @param pattern a non-localized pattern string
- * @param symbols the set of symbols to be used
- * @param infoInput the information used for currency plural format, including
- * currency plural patterns and plural rules.
- * @param style the decimal formatting style, it is one of the following values:
- * NumberFormat.NUMBERSTYLE; NumberFormat.CURRENCYSTYLE; NumberFormat.PERCENTSTYLE;
- * NumberFormat.SCIENTIFICSTYLE; NumberFormat.INTEGERSTYLE;
- * NumberFormat.ISOCURRENCYSTYLE; NumberFormat.PLURALCURRENCYSTYLE;
- * @stable ICU 4.2
- */
- public DecimalFormat_ICU58(String pattern, DecimalFormatSymbols symbols, CurrencyPluralInfo infoInput,
- int style) {
- CurrencyPluralInfo info = infoInput;
- if (style == NumberFormat.PLURALCURRENCYSTYLE) {
- info = (CurrencyPluralInfo) infoInput.clone();
- }
- create(pattern, symbols, info, style);
- }
-
- private void create(String pattern, DecimalFormatSymbols inputSymbols, CurrencyPluralInfo info,
- int inputStyle) {
- if (inputStyle != NumberFormat.PLURALCURRENCYSTYLE) {
- createFromPatternAndSymbols(pattern, inputSymbols);
- } else {
- // Always applyPattern after the symbols are set
- symbols = (DecimalFormatSymbols) inputSymbols.clone();
- currencyPluralInfo = info;
- // the pattern used in format is not fixed until formatting, in which, the
- // number is known and will be used to pick the right pattern based on plural
- // count. Here, set the pattern as the pattern of plural count == "other".
- // For most locale, the patterns are probably the same for all plural
- // count. If not, the right pattern need to be re-applied during format.
- String currencyPluralPatternForOther =
- currencyPluralInfo.getCurrencyPluralPattern("other");
- applyPatternWithoutExpandAffix(currencyPluralPatternForOther, false);
- setCurrencyForSymbols();
- }
- style = inputStyle;
- }
-
- /**
- * Creates a DecimalFormat for currency plural format from the given pattern, symbols,
- * and style.
- * @internal
- * @deprecated This API is ICU internal only.
- */
- @Deprecated
- public DecimalFormat_ICU58(String pattern, DecimalFormatSymbols inputSymbols, int style) {
- CurrencyPluralInfo info = null;
- if (style == NumberFormat.PLURALCURRENCYSTYLE) {
- info = new CurrencyPluralInfo(inputSymbols.getULocale());
- }
- create(pattern, inputSymbols, info, style);
- }
-
- /**
- * {@inheritDoc}
- * @stable ICU 2.0
- */
- @Override
- public StringBuffer format(double number, StringBuffer result, FieldPosition fieldPosition) {
- return format(number, result, fieldPosition, false);
- }
-
- // See if number is negative.
- // usage: isNegative(multiply(numberToBeFormatted));
- private boolean isNegative(double number) {
- // Detecting whether a double is negative is easy with the exception of the value
- // -0.0. This is a double which has a zero mantissa (and exponent), but a negative
- // sign bit. It is semantically distinct from a zero with a positive sign bit, and
- // this distinction is important to certain kinds of computations. However, it's a
- // little tricky to detect, since (-0.0 == 0.0) and !(-0.0 < 0.0). How then, you
- // may ask, does it behave distinctly from +0.0? Well, 1/(-0.0) ==
- // -Infinity. Proper detection of -0.0 is needed to deal with the issues raised by
- // bugs 4106658, 4106667, and 4147706. Liu 7/6/98.
- return (number < 0.0) || (number == 0.0 && 1 / number < 0.0);
- }
-
- // Rounds the number and strips of the negative sign.
- // usage: round(multiply(numberToBeFormatted))
- private double round(double number) {
- boolean isNegative = isNegative(number);
- if (isNegative)
- number = -number;
-
- // Apply rounding after multiplier
- if (roundingDouble > 0.0) {
- // number = roundingDouble
- // * round(number / roundingDouble, roundingMode, isNegative);
- return round(
- number, roundingDouble, roundingDoubleReciprocal, roundingMode,
- isNegative);
- }
- return number;
- }
-
- // Multiplies given number by multipler (if there is one) returning the new
- // number. If there is no multiplier, returns the number passed in unchanged.
- private double multiply(double number) {
- if (multiplier != 1) {
- return number * multiplier;
- }
- return number;
- }
-
- // [Spark/CDL] The actual method to format number. If boolean value
- // parseAttr == true, then attribute information will be recorded.
- private StringBuffer format(double number, StringBuffer result, FieldPosition fieldPosition,
- boolean parseAttr) {
- fieldPosition.setBeginIndex(0);
- fieldPosition.setEndIndex(0);
-
- if (Double.isNaN(number)) {
- if (fieldPosition.getField() == NumberFormat.INTEGER_FIELD) {
- fieldPosition.setBeginIndex(result.length());
- } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.INTEGER) {
- fieldPosition.setBeginIndex(result.length());
- }
-
- result.append(symbols.getNaN());
- // TODO: Combine setting a single FieldPosition or adding to an AttributedCharacterIterator
- // into a function like recordAttribute(FieldAttribute, begin, end).
-
- // [Spark/CDL] Add attribute for NaN here.
- // result.append(symbols.getNaN());
- if (parseAttr) {
- addAttribute(Field.INTEGER, result.length() - symbols.getNaN().length(),
- result.length());
- }
- if (fieldPosition.getField() == NumberFormat.INTEGER_FIELD) {
- fieldPosition.setEndIndex(result.length());
- } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.INTEGER) {
- fieldPosition.setEndIndex(result.length());
- }
-
- addPadding(result, fieldPosition, 0, 0);
- return result;
- }
-
- // Do this BEFORE checking to see if value is negative or infinite and
- // before rounding.
- number = multiply(number);
- boolean isNegative = isNegative(number);
- number = round(number);
-
- if (Double.isInfinite(number)) {
- int prefixLen = appendAffix(result, isNegative, true, fieldPosition, parseAttr);
-
- if (fieldPosition.getField() == NumberFormat.INTEGER_FIELD) {
- fieldPosition.setBeginIndex(result.length());
- } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.INTEGER) {
- fieldPosition.setBeginIndex(result.length());
- }
-
- // [Spark/CDL] Add attribute for infinity here.
- result.append(symbols.getInfinity());
- if (parseAttr) {
- addAttribute(Field.INTEGER, result.length() - symbols.getInfinity().length(),
- result.length());
- }
- if (fieldPosition.getField() == NumberFormat.INTEGER_FIELD) {
- fieldPosition.setEndIndex(result.length());
- } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.INTEGER) {
- fieldPosition.setEndIndex(result.length());
- }
-
- int suffixLen = appendAffix(result, isNegative, false, fieldPosition, parseAttr);
-
- addPadding(result, fieldPosition, prefixLen, suffixLen);
- return result;
- }
-
- int precision = precision(false);
-
- // This is to fix rounding for scientific notation. See ticket:10542.
- // This code should go away when a permanent fix is done for ticket:9931.
- //
- // This block of code only executes for scientific notation so it will not interfere with the
- // previous fix in {@link #resetActualRounding} for fixed decimal numbers.
- // Moreover this code only runs when there is rounding to be done (precision > 0) and when the
- // rounding mode is something other than ROUND_HALF_EVEN.
- // This block of code does the correct rounding of number in advance so that it will fit into
- // the number of digits indicated by precision. In this way, we avoid using the default
- // ROUND_HALF_EVEN behavior of DigitList. For example, if number = 0.003016 and roundingMode =
- // ROUND_DOWN and precision = 3 then after this code executes, number = 0.00301 (3 significant digits)
- if (useExponentialNotation && precision > 0 && number != 0.0 && roundingMode != BigDecimal.ROUND_HALF_EVEN) {
- int log10RoundingIncr = 1 - precision + (int) Math.floor(Math.log10(Math.abs(number)));
- double roundingIncReciprocal = 0.0;
- double roundingInc = 0.0;
- if (log10RoundingIncr < 0) {
- roundingIncReciprocal =
- BigDecimal.ONE.movePointRight(-log10RoundingIncr).doubleValue();
- } else {
- roundingInc =
- BigDecimal.ONE.movePointRight(log10RoundingIncr).doubleValue();
- }
- number = DecimalFormat_ICU58.round(number, roundingInc, roundingIncReciprocal, roundingMode, isNegative);
- }
- // End fix for ticket:10542
-
- // At this point we are guaranteed a nonnegative finite
- // number.
- synchronized (digitList) {
- digitList.set(number, precision, !useExponentialNotation &&
- !areSignificantDigitsUsed());
- return subformat(number, result, fieldPosition, isNegative, false, parseAttr);
- }
- }
-
- /**
- * This is a special function used by the CompactDecimalFormat subclass.
- * It completes only the rounding portion of the formatting and returns
- * the resulting double. CompactDecimalFormat uses the result to compute
- * the plural form to use.
- *
- * @param number The number to format.
- * @return The number rounded to the correct number of significant digits
- * with negative sign stripped off.
- * @internal
- * @deprecated This API is ICU internal only.
- */
- @Deprecated
- double adjustNumberAsInFormatting(double number) {
- if (Double.isNaN(number)) {
- return number;
- }
- number = round(multiply(number));
- if (Double.isInfinite(number)) {
- return number;
- }
- return toDigitList(number).getDouble();
- }
-
- @Deprecated
- DigitList toDigitList(double number) {
- DigitList result = new DigitList();
- result.set(number, precision(false), false);
- return result;
- }
-
- /**
- * This is a special function used by the CompactDecimalFormat subclass
- * to determine if the number to be formatted is negative.
- *
- * @param number The number to format.
- * @return True if number is negative.
- * @internal
- * @deprecated This API is ICU internal only.
- */
- @Deprecated
- boolean isNumberNegative(double number) {
- if (Double.isNaN(number)) {
- return false;
- }
- return isNegative(multiply(number));
- }
-
- /**
- * Round a double value to the nearest multiple of the given rounding increment,
- * according to the given mode. This is equivalent to rounding value/roundingInc to
- * the nearest integer, according to the given mode, and returning that integer *
- * roundingInc. Note this is changed from the version in 2.4, since division of
- * doubles have inaccuracies. jitterbug 1871.
- *
- * @param number
- * the absolute value of the number to be rounded
- * @param roundingInc
- * the rounding increment
- * @param roundingIncReciprocal
- * if non-zero, is the reciprocal of rounding inc.
- * @param mode
- * a BigDecimal rounding mode
- * @param isNegative
- * true if the number to be rounded is negative
- * @return the absolute value of the rounded result
- */
- private static double round(double number, double roundingInc, double roundingIncReciprocal,
- int mode, boolean isNegative) {
-
- double div = roundingIncReciprocal == 0.0 ? number / roundingInc : number *
- roundingIncReciprocal;
-
- // do the absolute cases first
-
- switch (mode) {
- case BigDecimal.ROUND_CEILING:
- div = (isNegative ? Math.floor(div + epsilon) : Math.ceil(div - epsilon));
- break;
- case BigDecimal.ROUND_FLOOR:
- div = (isNegative ? Math.ceil(div - epsilon) : Math.floor(div + epsilon));
- break;
- case BigDecimal.ROUND_DOWN:
- div = (Math.floor(div + epsilon));
- break;
- case BigDecimal.ROUND_UP:
- div = (Math.ceil(div - epsilon));
- break;
- case BigDecimal.ROUND_UNNECESSARY:
- if (div != Math.floor(div)) {
- throw new ArithmeticException("Rounding necessary");
- }
- return number;
- default:
-
- // Handle complex cases, where the choice depends on the closer value.
-
- // We figure out the distances to the two possible values, ceiling and floor.
- // We then go for the diff that is smaller. Only if they are equal does the
- // mode matter.
-
- double ceil = Math.ceil(div);
- double ceildiff = ceil - div; // (ceil * roundingInc) - number;
- double floor = Math.floor(div);
- double floordiff = div - floor; // number - (floor * roundingInc);
-
- // Note that the diff values were those mapped back to the "normal" space by
- // using the roundingInc. I don't have access to the original author of the
- // code but suspect that that was to produce better result in edge cases
- // because of machine precision, rather than simply using the difference
- // between, say, ceil and div. However, it didn't work in all cases. Am
- // trying instead using an epsilon value.
-
- switch (mode) {
- case BigDecimal.ROUND_HALF_EVEN:
- // We should be able to just return Math.rint(a), but this
- // doesn't work in some VMs.
- // if one is smaller than the other, take the corresponding side
- if (floordiff + epsilon < ceildiff) {
- div = floor;
- } else if (ceildiff + epsilon < floordiff) {
- div = ceil;
- } else { // they are equal, so we want to round to whichever is even
- double testFloor = floor / 2;
- div = (testFloor == Math.floor(testFloor)) ? floor : ceil;
- }
- break;
- case BigDecimal.ROUND_HALF_DOWN:
- div = ((floordiff <= ceildiff + epsilon) ? floor : ceil);
- break;
- case BigDecimal.ROUND_HALF_UP:
- div = ((ceildiff <= floordiff + epsilon) ? ceil : floor);
- break;
- default:
- throw new IllegalArgumentException("Invalid rounding mode: " + mode);
- }
- }
- number = roundingIncReciprocal == 0.0 ? div * roundingInc : div / roundingIncReciprocal;
- return number;
- }
-
- private static double epsilon = 0.00000000001;
-
- /**
- * @stable ICU 2.0
- */
- // [Spark/CDL] Delegate to format_long_StringBuffer_FieldPosition_boolean
- @Override
- public StringBuffer format(long number, StringBuffer result, FieldPosition fieldPosition) {
- return format(number, result, fieldPosition, false);
- }
-
- private StringBuffer format(long number, StringBuffer result, FieldPosition fieldPosition,
- boolean parseAttr) {
- fieldPosition.setBeginIndex(0);
- fieldPosition.setEndIndex(0);
-
- // If we are to do rounding, we need to move into the BigDecimal
- // domain in order to do divide/multiply correctly.
- if (actualRoundingIncrementICU != null) {
- return format(BigDecimal.valueOf(number), result, fieldPosition);
- }
-
- boolean isNegative = (number < 0);
- if (isNegative)
- number = -number;
-
- // In general, long values always represent real finite numbers, so we don't have
- // to check for +/- Infinity or NaN. However, there is one case we have to be
- // careful of: The multiplier can push a number near MIN_VALUE or MAX_VALUE
- // outside the legal range. We check for this before multiplying, and if it
- // happens we use BigInteger instead.
- if (multiplier != 1) {
- boolean tooBig = false;
- if (number < 0) { // This can only happen if number == Long.MIN_VALUE
- long cutoff = Long.MIN_VALUE / multiplier;
- tooBig = (number <= cutoff); // number == cutoff can only happen if multiplier == -1
- } else {
- long cutoff = Long.MAX_VALUE / multiplier;
- tooBig = (number > cutoff);
- }
- if (tooBig) {
- // [Spark/CDL] Use
- // format_BigInteger_StringBuffer_FieldPosition_boolean instead
- // parseAttr is used to judge whether to synthesize attributes.
- return format(BigInteger.valueOf(isNegative ? -number : number), result,
- fieldPosition, parseAttr);
- }
- }
-
- number *= multiplier;
- synchronized (digitList) {
- digitList.set(number, precision(true));
- // Issue 11808
- if (digitList.wasRounded() && roundingMode == BigDecimal.ROUND_UNNECESSARY) {
- throw new ArithmeticException("Rounding necessary");
- }
- return subformat(number, result, fieldPosition, isNegative, true, parseAttr);
- }
- }
-
- /**
- * Formats a BigInteger number.
- *
- * @stable ICU 2.0
- */
- @Override
- public StringBuffer format(BigInteger number, StringBuffer result,
- FieldPosition fieldPosition) {
- return format(number, result, fieldPosition, false);
- }
-
- private StringBuffer format(BigInteger number, StringBuffer result, FieldPosition fieldPosition,
- boolean parseAttr) {
- // If we are to do rounding, we need to move into the BigDecimal
- // domain in order to do divide/multiply correctly.
- if (actualRoundingIncrementICU != null) {
- return format(new BigDecimal(number), result, fieldPosition);
- }
-
- if (multiplier != 1) {
- number = number.multiply(BigInteger.valueOf(multiplier));
- }
-
- // At this point we are guaranteed a nonnegative finite
- // number.
- synchronized (digitList) {
- digitList.set(number, precision(true));
- // For issue 11808.
- if (digitList.wasRounded() && roundingMode == BigDecimal.ROUND_UNNECESSARY) {
- throw new ArithmeticException("Rounding necessary");
- }
- return subformat(number.intValue(), result, fieldPosition, number.signum() < 0, true,
- parseAttr);
- }
- }
-
- /**
- * Formats a BigDecimal number.
- *
- * @stable ICU 2.0
- */
- @Override
- public StringBuffer format(java.math.BigDecimal number, StringBuffer result,
- FieldPosition fieldPosition) {
- return format(number, result, fieldPosition, false);
- }
-
- private StringBuffer format(java.math.BigDecimal number, StringBuffer result,
- FieldPosition fieldPosition,
- boolean parseAttr) {
- if (multiplier != 1) {
- number = number.multiply(java.math.BigDecimal.valueOf(multiplier));
- }
-
- if (actualRoundingIncrement != null) {
- number = number.divide(actualRoundingIncrement, 0, roundingMode).multiply(actualRoundingIncrement);
- }
-
- synchronized (digitList) {
- digitList.set(number, precision(false), !useExponentialNotation &&
- !areSignificantDigitsUsed());
- // For issue 11808.
- if (digitList.wasRounded() && roundingMode == BigDecimal.ROUND_UNNECESSARY) {
- throw new ArithmeticException("Rounding necessary");
- }
- return subformat(number.doubleValue(), result, fieldPosition, number.signum() < 0,
- false, parseAttr);
- }
- }
-
- /**
- * Formats a BigDecimal number.
- *
- * @stable ICU 2.0
- */
- @Override
- public StringBuffer format(BigDecimal number, StringBuffer result,
- FieldPosition fieldPosition) {
- // This method is just a copy of the corresponding java.math.BigDecimal method
- // for now. It isn't very efficient since it must create a conversion object to
- // do math on the rounding increment. In the future we may try to clean this up,
- // or even better, limit our support to just one flavor of BigDecimal.
- if (multiplier != 1) {
- number = number.multiply(BigDecimal.valueOf(multiplier), mathContext);
- }
-
- if (actualRoundingIncrementICU != null) {
- number = number.divide(actualRoundingIncrementICU, 0, roundingMode)
- .multiply(actualRoundingIncrementICU, mathContext);
- }
-
- synchronized (digitList) {
- digitList.set(number, precision(false), !useExponentialNotation &&
- !areSignificantDigitsUsed());
- // For issue 11808.
- if (digitList.wasRounded() && roundingMode == BigDecimal.ROUND_UNNECESSARY) {
- throw new ArithmeticException("Rounding necessary");
- }
- return subformat(number.doubleValue(), result, fieldPosition, number.signum() < 0,
- false, false);
- }
- }
-
- /**
- * Returns true if a grouping separator belongs at the given position, based on whether
- * grouping is in use and the values of the primary and secondary grouping interval.
- *
- * @param pos the number of integer digits to the right of the current position. Zero
- * indicates the position after the rightmost integer digit.
- * @return true if a grouping character belongs at the current position.
- */
- private boolean isGroupingPosition(int pos) {
- boolean result = false;
- if (isGroupingUsed() && (pos > 0) && (groupingSize > 0)) {
- if ((groupingSize2 > 0) && (pos > groupingSize)) {
- result = ((pos - groupingSize) % groupingSize2) == 0;
- } else {
- result = pos % groupingSize == 0;
- }
- }
- return result;
- }
-
- /**
- * Return the number of fraction digits to display, or the total
- * number of digits for significant digit formats and exponential
- * formats.
- */
- private int precision(boolean isIntegral) {
- if (areSignificantDigitsUsed()) {
- return getMaximumSignificantDigits();
- } else if (useExponentialNotation) {
- return getMinimumIntegerDigits() + getMaximumFractionDigits();
- } else {
- return isIntegral ? 0 : getMaximumFractionDigits();
- }
- }
-
- private StringBuffer subformat(int number, StringBuffer result, FieldPosition fieldPosition,
- boolean isNegative, boolean isInteger, boolean parseAttr) {
- if (currencySignCount == CURRENCY_SIGN_COUNT_IN_PLURAL_FORMAT) {
- // compute the plural category from the digitList plus other settings
- return subformat(currencyPluralInfo.select(getFixedDecimal(number)),
- result, fieldPosition, isNegative,
- isInteger, parseAttr);
- } else {
- return subformat(result, fieldPosition, isNegative, isInteger, parseAttr);
- }
- }
-
- /**
- * This is ugly, but don't see a better way to do it without major restructuring of the code.
- */
- /*package*/ FixedDecimal getFixedDecimal(double number) {
- // get the visible fractions and the number of fraction digits.
- return getFixedDecimal(number, digitList);
- }
-
- FixedDecimal getFixedDecimal(double number, DigitList dl) {
- int fractionalDigitsInDigitList = dl.count - dl.decimalAt;
- int v;
- long f;
- int maxFractionalDigits;
- int minFractionalDigits;
- if (useSignificantDigits) {
- maxFractionalDigits = maxSignificantDigits - dl.decimalAt;
- minFractionalDigits = minSignificantDigits - dl.decimalAt;
- if (minFractionalDigits < 0) {
- minFractionalDigits = 0;
- }
- if (maxFractionalDigits < 0) {
- maxFractionalDigits = 0;
- }
- } else {
- maxFractionalDigits = getMaximumFractionDigits();
- minFractionalDigits = getMinimumFractionDigits();
- }
- v = fractionalDigitsInDigitList;
- if (v < minFractionalDigits) {
- v = minFractionalDigits;
- } else if (v > maxFractionalDigits) {
- v = maxFractionalDigits;
- }
- f = 0;
- if (v > 0) {
- for (int i = Math.max(0, dl.decimalAt); i < dl.count; ++i) {
- f *= 10;
- f += (dl.digits[i] - '0');
- }
- for (int i = v; i < fractionalDigitsInDigitList; ++i) {
- f *= 10;
- }
- }
- return new FixedDecimal(number, v, f);
- }
-
- private StringBuffer subformat(double number, StringBuffer result, FieldPosition fieldPosition,
- boolean isNegative,
- boolean isInteger, boolean parseAttr) {
- if (currencySignCount == CURRENCY_SIGN_COUNT_IN_PLURAL_FORMAT) {
- // compute the plural category from the digitList plus other settings
- return subformat(currencyPluralInfo.select(getFixedDecimal(number)),
- result, fieldPosition, isNegative,
- isInteger, parseAttr);
- } else {
- return subformat(result, fieldPosition, isNegative, isInteger, parseAttr);
- }
- }
-
- private StringBuffer subformat(String pluralCount, StringBuffer result, FieldPosition fieldPosition,
- boolean isNegative, boolean isInteger, boolean parseAttr) {
- // There are 2 ways to activate currency plural format: by applying a pattern with
- // 3 currency sign directly, or by instantiate a decimal formatter using
- // PLURALCURRENCYSTYLE. For both cases, the number of currency sign in the
- // pattern is 3. Even if the number of currency sign in the pattern is 3, it does
- // not mean we need to reset the pattern. For 1st case, we do not need to reset
- // pattern. For 2nd case, we might need to reset pattern, if the default pattern
- // (corresponding to plural count 'other') we use is different from the pattern
- // based on 'pluralCount'.
- //
- // style is only valid when decimal formatter is constructed through
- // DecimalFormat(pattern, symbol, style)
- if (style == NumberFormat.PLURALCURRENCYSTYLE) {
- // May need to reset pattern if the style is PLURALCURRENCYSTYLE.
- String currencyPluralPattern = currencyPluralInfo.getCurrencyPluralPattern(pluralCount);
- if (formatPattern.equals(currencyPluralPattern) == false) {
- applyPatternWithoutExpandAffix(currencyPluralPattern, false);
- }
- }
- // Expand the affix to the right name according to the plural rule. This is only
- // used for currency plural formatting. Currency plural name is not a fixed
- // static one, it is a dynamic name based on the currency plural count. So, the
- // affixes need to be expanded here. For other cases, the affix is a static one
- // based on pattern alone, and it is already expanded during applying pattern, or
- // setDecimalFormatSymbols, or setCurrency.
- expandAffixAdjustWidth(pluralCount);
- return subformat(result, fieldPosition, isNegative, isInteger, parseAttr);
- }
-
- /**
- * Complete the formatting of a finite number. On entry, the
- * digitList must be filled in with the correct digits.
- */
- private StringBuffer subformat(StringBuffer result, FieldPosition fieldPosition,
- boolean isNegative, boolean isInteger, boolean parseAttr) {
- // NOTE: This isn't required anymore because DigitList takes care of this.
- //
- // // The negative of the exponent represents the number of leading // zeros
- // between the decimal and the first non-zero digit, for // a value < 0.1 (e.g.,
- // for 0.00123, -fExponent == 2). If this // is more than the maximum fraction
- // digits, then we have an underflow // for the printed representation. We
- // recognize this here and set // the DigitList representation to zero in this
- // situation.
- //
- // if (-digitList.decimalAt >= getMaximumFractionDigits())
- // {
- // digitList.count = 0;
- // }
-
-
-
- // Per bug 4147706, DecimalFormat must respect the sign of numbers which format as
- // zero. This allows sensible computations and preserves relations such as
- // signum(1/x) = signum(x), where x is +Infinity or -Infinity. Prior to this fix,
- // we always formatted zero values as if they were positive. Liu 7/6/98.
- if (digitList.isZero()) {
- digitList.decimalAt = 0; // Normalize
- }
-
- int prefixLen = appendAffix(result, isNegative, true, fieldPosition, parseAttr);
-
- if (useExponentialNotation) {
- subformatExponential(result, fieldPosition, parseAttr);
- } else {
- subformatFixed(result, fieldPosition, isInteger, parseAttr);
- }
-
- int suffixLen = appendAffix(result, isNegative, false, fieldPosition, parseAttr);
- addPadding(result, fieldPosition, prefixLen, suffixLen);
- return result;
- }
-
- private void subformatFixed(StringBuffer result,
- FieldPosition fieldPosition,
- boolean isInteger,
- boolean parseAttr) {
- String[] digits = symbols.getDigitStrings();
-
- String grouping = currencySignCount == CURRENCY_SIGN_COUNT_ZERO ?
- symbols.getGroupingSeparatorString(): symbols.getMonetaryGroupingSeparatorString();
- String decimal = currencySignCount == CURRENCY_SIGN_COUNT_ZERO ?
- symbols.getDecimalSeparatorString() : symbols.getMonetaryDecimalSeparatorString();
- boolean useSigDig = areSignificantDigitsUsed();
- int maxIntDig = getMaximumIntegerDigits();
- int minIntDig = getMinimumIntegerDigits();
- int i;
- // [Spark/CDL] Record the integer start index.
- int intBegin = result.length();
- // Record field information for caller.
- if (fieldPosition.getField() == NumberFormat.INTEGER_FIELD ||
- fieldPosition.getFieldAttribute() == NumberFormat.Field.INTEGER) {
- fieldPosition.setBeginIndex(intBegin);
- }
- long fractionalDigits = 0;
- int fractionalDigitsCount = 0;
- boolean recordFractionDigits = false;
-
- int sigCount = 0;
- int minSigDig = getMinimumSignificantDigits();
- int maxSigDig = getMaximumSignificantDigits();
- if (!useSigDig) {
- minSigDig = 0;
- maxSigDig = Integer.MAX_VALUE;
- }
-
- // Output the integer portion. Here 'count' is the total number of integer
- // digits we will display, including both leading zeros required to satisfy
- // getMinimumIntegerDigits, and actual digits present in the number.
- int count = useSigDig ? Math.max(1, digitList.decimalAt) : minIntDig;
- if (digitList.decimalAt > 0 && count < digitList.decimalAt) {
- count = digitList.decimalAt;
- }
-
- // Handle the case where getMaximumIntegerDigits() is smaller than the real
- // number of integer digits. If this is so, we output the least significant
- // max integer digits. For example, the value 1997 printed with 2 max integer
- // digits is just "97".
-
- int digitIndex = 0; // Index into digitList.fDigits[]
- if (count > maxIntDig && maxIntDig >= 0) {
- count = maxIntDig;
- digitIndex = digitList.decimalAt - count;
- }
-
- int sizeBeforeIntegerPart = result.length();
- for (i = count - 1; i >= 0; --i) {
- if (i < digitList.decimalAt && digitIndex < digitList.count
- && sigCount < maxSigDig) {
- // Output a real digit
- result.append(digits[digitList.getDigitValue(digitIndex++)]);
- ++sigCount;
- } else {
- // Output a zero (leading or trailing)
- result.append(digits[0]);
- if (sigCount > 0) {
- ++sigCount;
- }
- }
-
- // Output grouping separator if necessary.
- if (isGroupingPosition(i)) {
- result.append(grouping);
- // [Spark/CDL] Add grouping separator attribute here.
- // Set only for the first instance.
- // Length of grouping separator is 1.
- if (fieldPosition.getFieldAttribute() == Field.GROUPING_SEPARATOR &&
- fieldPosition.getBeginIndex() == 0 && fieldPosition.getEndIndex() == 0) {
- fieldPosition.setBeginIndex(result.length()-1);
- fieldPosition.setEndIndex(result.length());
- }
- if (parseAttr) {
- addAttribute(Field.GROUPING_SEPARATOR, result.length() - 1, result.length());
- }
- }
- }
-
- // Record field information for caller.
- if (fieldPosition.getField() == NumberFormat.INTEGER_FIELD ||
- fieldPosition.getFieldAttribute() == NumberFormat.Field.INTEGER) {
- fieldPosition.setEndIndex(result.length());
- }
-
- // This handles the special case of formatting 0. For zero only, we count the
- // zero to the left of the decimal point as one signficant digit. Ordinarily we
- // do not count any leading 0's as significant. If the number we are formatting
- // is not zero, then either sigCount or digits.getCount() will be non-zero.
- if (sigCount == 0 && digitList.count == 0) {
- sigCount = 1;
- }
-
- // Determine whether or not there are any printable fractional digits. If
- // we've used up the digits we know there aren't.
- boolean fractionPresent = (!isInteger && digitIndex < digitList.count)
- || (useSigDig ? (sigCount < minSigDig) : (getMinimumFractionDigits() > 0));
-
- // If there is no fraction present, and we haven't printed any integer digits,
- // then print a zero. Otherwise we won't print _any_ digits, and we won't be
- // able to parse this string.
- if (!fractionPresent && result.length() == sizeBeforeIntegerPart)
- result.append(digits[0]);
- // [Spark/CDL] Add attribute for integer part.
- if (parseAttr) {
- addAttribute(Field.INTEGER, intBegin, result.length());
- }
- // Output the decimal separator if we always do so.
- if (decimalSeparatorAlwaysShown || fractionPresent) {
- if (fieldPosition.getFieldAttribute() == Field.DECIMAL_SEPARATOR) {
- fieldPosition.setBeginIndex(result.length());
- }
- result.append(decimal);
- if (fieldPosition.getFieldAttribute() == Field.DECIMAL_SEPARATOR) {
- fieldPosition.setEndIndex(result.length());
- }
- // [Spark/CDL] Add attribute for decimal separator
- if (parseAttr) {
- addAttribute(Field.DECIMAL_SEPARATOR, result.length() - 1, result.length());
- }
- }
-
- // Record field information for caller.
- if (fieldPosition.getField() == NumberFormat.FRACTION_FIELD) {
- fieldPosition.setBeginIndex(result.length());
- } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.FRACTION) {
- fieldPosition.setBeginIndex(result.length());
- }
-
- // [Spark/CDL] Record the begin index of fraction part.
- int fracBegin = result.length();
- recordFractionDigits = fieldPosition instanceof UFieldPosition;
-
- count = useSigDig ? Integer.MAX_VALUE : getMaximumFractionDigits();
- if (useSigDig && (sigCount == maxSigDig ||
- (sigCount >= minSigDig && digitIndex == digitList.count))) {
- count = 0;
- }
- for (i = 0; i < count; ++i) {
- // Here is where we escape from the loop. We escape if we've output the
- // maximum fraction digits (specified in the for expression above). We
- // also stop when we've output the minimum digits and either: we have an
- // integer, so there is no fractional stuff to display, or we're out of
- // significant digits.
- if (!useSigDig && i >= getMinimumFractionDigits() &&
- (isInteger || digitIndex >= digitList.count)) {
- break;
- }
-
- // Output leading fractional zeros. These are zeros that come after the
- // decimal but before any significant digits. These are only output if
- // abs(number being formatted) < 1.0.
- if (-1 - i > (digitList.decimalAt - 1)) {
- result.append(digits[0]);
- if (recordFractionDigits) {
- ++fractionalDigitsCount;
- fractionalDigits *= 10;
- }
- continue;
- }
-
- // Output a digit, if we have any precision left, or a zero if we
- // don't. We don't want to output noise digits.
- if (!isInteger && digitIndex < digitList.count) {
- byte digit = digitList.getDigitValue(digitIndex++);
- result.append(digits[digit]);
- if (recordFractionDigits) {
- ++fractionalDigitsCount;
- fractionalDigits *= 10;
- fractionalDigits += digit;
- }
- } else {
- result.append(digits[0]);
- if (recordFractionDigits) {
- ++fractionalDigitsCount;
- fractionalDigits *= 10;
- }
- }
-
- // If we reach the maximum number of significant digits, or if we output
- // all the real digits and reach the minimum, then we are done.
- ++sigCount;
- if (useSigDig && (sigCount == maxSigDig ||
- (digitIndex == digitList.count && sigCount >= minSigDig))) {
- break;
- }
- }
-
- // Record field information for caller.
- if (fieldPosition.getField() == NumberFormat.FRACTION_FIELD) {
- fieldPosition.setEndIndex(result.length());
- } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.FRACTION) {
- fieldPosition.setEndIndex(result.length());
- }
- if (recordFractionDigits) {
- ((UFieldPosition) fieldPosition).setFractionDigits(fractionalDigitsCount, fractionalDigits);
- }
-
- // [Spark/CDL] Add attribute information if necessary.
- if (parseAttr && (decimalSeparatorAlwaysShown || fractionPresent)) {
- addAttribute(Field.FRACTION, fracBegin, result.length());
- }
- }
-
- private void subformatExponential(StringBuffer result,
- FieldPosition fieldPosition,
- boolean parseAttr) {
- String[] digits = symbols.getDigitStringsLocal();
- String decimal = currencySignCount == CURRENCY_SIGN_COUNT_ZERO ?
- symbols.getDecimalSeparatorString() : symbols.getMonetaryDecimalSeparatorString();
- boolean useSigDig = areSignificantDigitsUsed();
- int maxIntDig = getMaximumIntegerDigits();
- int minIntDig = getMinimumIntegerDigits();
- int i;
- // Record field information for caller.
- if (fieldPosition.getField() == NumberFormat.INTEGER_FIELD) {
- fieldPosition.setBeginIndex(result.length());
- fieldPosition.setEndIndex(-1);
- } else if (fieldPosition.getField() == NumberFormat.FRACTION_FIELD) {
- fieldPosition.setBeginIndex(-1);
- } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.INTEGER) {
- fieldPosition.setBeginIndex(result.length());
- fieldPosition.setEndIndex(-1);
- } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.FRACTION) {
- fieldPosition.setBeginIndex(-1);
- }
-
- // [Spark/CDL]
- // the begin index of integer part
- // the end index of integer part
- // the begin index of fractional part
- int intBegin = result.length();
- int intEnd = -1;
- int fracBegin = -1;
- int minFracDig = 0;
- if (useSigDig) {
- maxIntDig = minIntDig = 1;
- minFracDig = getMinimumSignificantDigits() - 1;
- } else {
- minFracDig = getMinimumFractionDigits();
- if (maxIntDig > MAX_SCIENTIFIC_INTEGER_DIGITS) {
- maxIntDig = 1;
- if (maxIntDig < minIntDig) {
- maxIntDig = minIntDig;
- }
- }
- if (maxIntDig > minIntDig) {
- minIntDig = 1;
- }
- }
- long fractionalDigits = 0;
- int fractionalDigitsCount = 0;
- boolean recordFractionDigits = false;
-
- // Minimum integer digits are handled in exponential format by adjusting the
- // exponent. For example, 0.01234 with 3 minimum integer digits is "123.4E-4".
-
- // Maximum integer digits are interpreted as indicating the repeating
- // range. This is useful for engineering notation, in which the exponent is
- // restricted to a multiple of 3. For example, 0.01234 with 3 maximum integer
- // digits is "12.34e-3". If maximum integer digits are defined and are larger
- // than minimum integer digits, then minimum integer digits are ignored.
-
- int exponent = digitList.decimalAt;
- if (maxIntDig > 1 && maxIntDig != minIntDig) {
- // A exponent increment is defined; adjust to it.
- exponent = (exponent > 0) ? (exponent - 1) / maxIntDig : (exponent / maxIntDig) - 1;
- exponent *= maxIntDig;
- } else {
- // No exponent increment is defined; use minimum integer digits.
- // If none is specified, as in "#E0", generate 1 integer digit.
- exponent -= (minIntDig > 0 || minFracDig > 0) ? minIntDig : 1;
- }
-
- // We now output a minimum number of digits, and more if there are more
- // digits, up to the maximum number of digits. We place the decimal point
- // after the "integer" digits, which are the first (decimalAt - exponent)
- // digits.
- int minimumDigits = minIntDig + minFracDig;
- // The number of integer digits is handled specially if the number
- // is zero, since then there may be no digits.
- int integerDigits = digitList.isZero() ? minIntDig : digitList.decimalAt - exponent;
- int totalDigits = digitList.count;
- if (minimumDigits > totalDigits)
- totalDigits = minimumDigits;
- if (integerDigits > totalDigits)
- totalDigits = integerDigits;
-
- for (i = 0; i < totalDigits; ++i) {
- if (i == integerDigits) {
- // Record field information for caller.
- if (fieldPosition.getField() == NumberFormat.INTEGER_FIELD) {
- fieldPosition.setEndIndex(result.length());
- } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.INTEGER) {
- fieldPosition.setEndIndex(result.length());
- }
-
- // [Spark/CDL] Add attribute for integer part
- if (parseAttr) {
- intEnd = result.length();
- addAttribute(Field.INTEGER, intBegin, result.length());
- }
- if (fieldPosition.getFieldAttribute() == Field.DECIMAL_SEPARATOR) {
- fieldPosition.setBeginIndex(result.length());
- }
- result.append(decimal);
- if (fieldPosition.getFieldAttribute() == Field.DECIMAL_SEPARATOR) {
- fieldPosition.setEndIndex(result.length());
- }
- // [Spark/CDL] Add attribute for decimal separator
- fracBegin = result.length();
- if (parseAttr) {
- // Length of decimal separator is 1.
- int decimalSeparatorBegin = result.length() - 1;
- addAttribute(Field.DECIMAL_SEPARATOR, decimalSeparatorBegin,
- result.length());
- }
- // Record field information for caller.
- if (fieldPosition.getField() == NumberFormat.FRACTION_FIELD) {
- fieldPosition.setBeginIndex(result.length());
- } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.FRACTION) {
- fieldPosition.setBeginIndex(result.length());
- }
- recordFractionDigits = fieldPosition instanceof UFieldPosition;
-
- }
- byte digit = (i < digitList.count) ? digitList.getDigitValue(i) : (byte)0;
- result.append(digits[digit]);
- if (recordFractionDigits) {
- ++fractionalDigitsCount;
- fractionalDigits *= 10;
- fractionalDigits += digit;
- }
- }
-
- // For ICU compatibility and format 0 to 0E0 with pattern "#E0" [Richard/GCL]
- if (digitList.isZero() && (totalDigits == 0)) {
- result.append(digits[0]);
- }
-
- // add the decimal separator if it is to be always shown AND there are no decimal digits
- if ((fracBegin == -1) && this.decimalSeparatorAlwaysShown) {
- if (fieldPosition.getFieldAttribute() == Field.DECIMAL_SEPARATOR) {
- fieldPosition.setBeginIndex(result.length());
- }
- result.append(decimal);
- if (fieldPosition.getFieldAttribute() == Field.DECIMAL_SEPARATOR) {
- fieldPosition.setEndIndex(result.length());
- }
- if (parseAttr) {
- // Length of decimal separator is 1.
- int decimalSeparatorBegin = result.length() - 1;
- addAttribute(Field.DECIMAL_SEPARATOR, decimalSeparatorBegin, result.length());
- }
- }
-
- // Record field information
- if (fieldPosition.getField() == NumberFormat.INTEGER_FIELD) {
- if (fieldPosition.getEndIndex() < 0) {
- fieldPosition.setEndIndex(result.length());
- }
- } else if (fieldPosition.getField() == NumberFormat.FRACTION_FIELD) {
- if (fieldPosition.getBeginIndex() < 0) {
- fieldPosition.setBeginIndex(result.length());
- }
- fieldPosition.setEndIndex(result.length());
- } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.INTEGER) {
- if (fieldPosition.getEndIndex() < 0) {
- fieldPosition.setEndIndex(result.length());
- }
- } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.FRACTION) {
- if (fieldPosition.getBeginIndex() < 0) {
- fieldPosition.setBeginIndex(result.length());
- }
- fieldPosition.setEndIndex(result.length());
- }
- if (recordFractionDigits) {
- ((UFieldPosition) fieldPosition).setFractionDigits(fractionalDigitsCount, fractionalDigits);
- }
-
- // [Spark/CDL] Calculate the end index of integer part and fractional
- // part if they are not properly processed yet.
- if (parseAttr) {
- if (intEnd < 0) {
- addAttribute(Field.INTEGER, intBegin, result.length());
- }
- if (fracBegin > 0) {
- addAttribute(Field.FRACTION, fracBegin, result.length());
- }
- }
-
- // The exponent is output using the pattern-specified minimum exponent
- // digits. There is no maximum limit to the exponent digits, since truncating
- // the exponent would result in an unacceptable inaccuracy.
- if (fieldPosition.getFieldAttribute() == Field.EXPONENT_SYMBOL) {
- fieldPosition.setBeginIndex(result.length());
- }
-
- result.append(symbols.getExponentSeparator());
- if (fieldPosition.getFieldAttribute() == Field.EXPONENT_SYMBOL) {
- fieldPosition.setEndIndex(result.length());
- }
- // [Spark/CDL] For exponent symbol, add an attribute.
- if (parseAttr) {
- addAttribute(Field.EXPONENT_SYMBOL, result.length() -
- symbols.getExponentSeparator().length(), result.length());
- }
- // For zero values, we force the exponent to zero. We must do this here, and
- // not earlier, because the value is used to determine integer digit count
- // above.
- if (digitList.isZero())
- exponent = 0;
-
- boolean negativeExponent = exponent < 0;
- if (negativeExponent) {
- exponent = -exponent;
- if (fieldPosition.getFieldAttribute() == Field.EXPONENT_SIGN) {
- fieldPosition.setBeginIndex(result.length());
- }
- result.append(symbols.getMinusSignString());
- if (fieldPosition.getFieldAttribute() == Field.EXPONENT_SIGN) {
- fieldPosition.setEndIndex(result.length());
- }
- // [Spark/CDL] If exponent has sign, then add an exponent sign
- // attribute.
- if (parseAttr) {
- // Length of exponent sign is 1.
- addAttribute(Field.EXPONENT_SIGN, result.length() - 1, result.length());
- }
- } else if (exponentSignAlwaysShown) {
- if (fieldPosition.getFieldAttribute() == Field.EXPONENT_SIGN) {
- fieldPosition.setBeginIndex(result.length());
- }
- result.append(symbols.getPlusSignString());
- if (fieldPosition.getFieldAttribute() == Field.EXPONENT_SIGN) {
- fieldPosition.setEndIndex(result.length());
- }
- // [Spark/CDL] Add an plus sign attribute.
- if (parseAttr) {
- // Length of exponent sign is 1.
- int expSignBegin = result.length() - 1;
- addAttribute(Field.EXPONENT_SIGN, expSignBegin, result.length());
- }
- }
- int expBegin = result.length();
- digitList.set(exponent);
- {
- int expDig = minExponentDigits;
- if (useExponentialNotation && expDig < 1) {
- expDig = 1;
- }
- for (i = digitList.decimalAt; i < expDig; ++i)
- result.append(digits[0]);
- }
- for (i = 0; i < digitList.decimalAt; ++i) {
- result.append((i < digitList.count) ? digits[digitList.getDigitValue(i)]
- : digits[0]);
- }
- // [Spark/CDL] Add attribute for exponent part.
- if (fieldPosition.getFieldAttribute() == Field.EXPONENT) {
- fieldPosition.setBeginIndex(expBegin);
- fieldPosition.setEndIndex(result.length());
- }
- if (parseAttr) {
- addAttribute(Field.EXPONENT, expBegin, result.length());
- }
- }
-
- private final void addPadding(StringBuffer result, FieldPosition fieldPosition, int prefixLen,
- int suffixLen) {
- if (formatWidth > 0) {
- int len = formatWidth - result.length();
- if (len > 0) {
- char[] padding = new char[len];
- for (int i = 0; i < len; ++i) {
- padding[i] = pad;
- }
- switch (padPosition) {
- case PAD_AFTER_PREFIX:
- result.insert(prefixLen, padding);
- break;
- case PAD_BEFORE_PREFIX:
- result.insert(0, padding);
- break;
- case PAD_BEFORE_SUFFIX:
- result.insert(result.length() - suffixLen, padding);
- break;
- case PAD_AFTER_SUFFIX:
- result.append(padding);
- break;
- }
- if (padPosition == PAD_BEFORE_PREFIX || padPosition == PAD_AFTER_PREFIX) {
- fieldPosition.setBeginIndex(fieldPosition.getBeginIndex() + len);
- fieldPosition.setEndIndex(fieldPosition.getEndIndex() + len);
- }
- }
- }
- }
-
- /**
- * Parses the given string, returning a <code>Number</code> object to represent the
- * parsed value. <code>Double</code> objects are returned to represent non-integral
- * values which cannot be stored in a <code>BigDecimal</code>. These are
- * <code>NaN</code>, infinity, -infinity, and -0.0. If {@link #isParseBigDecimal()} is
- * false (the default), all other values are returned as <code>Long</code>,
- * <code>BigInteger</code>, or <code>BigDecimal</code> values, in that order of
- * preference. If {@link #isParseBigDecimal()} is true, all other values are returned
- * as <code>BigDecimal</code> valuse. If the parse fails, null is returned.
- *
- * @param text the string to be parsed
- * @param parsePosition defines the position where parsing is to begin, and upon
- * return, the position where parsing left off. If the position has not changed upon
- * return, then parsing failed.
- * @return a <code>Number</code> object with the parsed value or
- * <code>null</code> if the parse failed
- * @stable ICU 2.0
- */
- @Override
- public Number parse(String text, ParsePosition parsePosition) {
- return (Number) parse(text, parsePosition, null);
- }
-
- /**
- * Parses text from the given string as a CurrencyAmount. Unlike the parse() method,
- * this method will attempt to parse a generic currency name, searching for a match of
- * this object's locale's currency display names, or for a 3-letter ISO currency
- * code. This method will fail if this format is not a currency format, that is, if it
- * does not contain the currency pattern symbol (U+00A4) in its prefix or suffix.
- *
- * @param text the text to parse
- * @param pos input-output position; on input, the position within text to match; must
- * have 0 <= pos.getIndex() < text.length(); on output, the position after the last
- * matched character. If the parse fails, the position in unchanged upon output.
- * @return a CurrencyAmount, or null upon failure
- * @stable ICU 49
- */
- @Override
- public CurrencyAmount parseCurrency(CharSequence text, ParsePosition pos) {
- Currency[] currency = new Currency[1];
- return (CurrencyAmount) parse(text.toString(), pos, currency);
- }
-
- /**
- * Parses the given text as either a Number or a CurrencyAmount.
- *
- * @param text the string to parse
- * @param parsePosition input-output position; on input, the position within text to
- * match; must have 0 <= pos.getIndex() < text.length(); on output, the position after
- * the last matched character. If the parse fails, the position in unchanged upon
- * output.
- * @param currency if non-null, a CurrencyAmount is parsed and returned; otherwise a
- * Number is parsed and returned
- * @return a Number or CurrencyAmount or null
- */
- private Object parse(String text, ParsePosition parsePosition, Currency[] currency) {
- int backup;
- int i = backup = parsePosition.getIndex();
-
- // Handle NaN as a special case:
-
- // Skip padding characters, if around prefix
- if (formatWidth > 0 &&
- (padPosition == PAD_BEFORE_PREFIX || padPosition == PAD_AFTER_PREFIX)) {
- i = skipPadding(text, i);
- }
- if (text.regionMatches(i, symbols.getNaN(), 0, symbols.getNaN().length())) {
- i += symbols.getNaN().length();
- // Skip padding characters, if around suffix
- if (formatWidth > 0 && (padPosition == PAD_BEFORE_SUFFIX ||
- padPosition == PAD_AFTER_SUFFIX)) {
- i = skipPadding(text, i);
- }
- parsePosition.setIndex(i);
- return new Double(Double.NaN);
- }
-
- // NaN parse failed; start over
- i = backup;
-
- boolean[] status = new boolean[STATUS_LENGTH];
- if (currencySignCount != CURRENCY_SIGN_COUNT_ZERO) {
- if (!parseForCurrency(text, parsePosition, currency, status)) {
- return null;
- }
- } else if (currency != null) {
- return null;
- } else {
- if (!subparse(text, parsePosition, digitList, status, currency, negPrefixPattern,
- negSuffixPattern, posPrefixPattern, posSuffixPattern,
- false, Currency.SYMBOL_NAME)) {
- parsePosition.setIndex(backup);
- return null;
- }
- }
-
- Number n = null;
-
- // Handle infinity
- if (status[STATUS_INFINITE]) {
- n = new Double(status[STATUS_POSITIVE] ? Double.POSITIVE_INFINITY :
- Double.NEGATIVE_INFINITY);
- }
-
- // Handle underflow
- else if (status[STATUS_UNDERFLOW]) {
- n = status[STATUS_POSITIVE] ? new Double("0.0") : new Double("-0.0");
- }
-
- // Handle -0.0
- else if (!status[STATUS_POSITIVE] && digitList.isZero()) {
- n = new Double("-0.0");
- }
-
- else {
- // Do as much of the multiplier conversion as possible without
- // losing accuracy.
- int mult = multiplier; // Don't modify this.multiplier
- while (mult % 10 == 0) {
- --digitList.decimalAt;
- mult /= 10;
- }
-
- // Handle integral values
- if (!parseBigDecimal && mult == 1 && digitList.isIntegral()) {
- // hack quick long
- if (digitList.decimalAt < 12) { // quick check for long
- long l = 0;
- if (digitList.count > 0) {
- int nx = 0;
- while (nx < digitList.count) {
- l = l * 10 + (char) digitList.digits[nx++] - '0';
- }
- while (nx++ < digitList.decimalAt) {
- l *= 10;
- }
- if (!status[STATUS_POSITIVE]) {
- l = -l;
- }
- }
- n = Long.valueOf(l);
- } else {
- BigInteger big = digitList.getBigInteger(status[STATUS_POSITIVE]);
- n = (big.bitLength() < 64) ? (Number) Long.valueOf(big.longValue()) : (Number) big;
- }
- }
- // Handle non-integral values or the case where parseBigDecimal is set
- else {
- BigDecimal big = digitList.getBigDecimalICU(status[STATUS_POSITIVE]);
- n = big;
- if (mult != 1) {
- n = big.divide(BigDecimal.valueOf(mult), mathContext);
- }
- }
- }
-
- // Assemble into CurrencyAmount if necessary
- return (currency != null) ? (Object) new CurrencyAmount(n, currency[0]) : (Object) n;
- }
-
- private boolean parseForCurrency(String text, ParsePosition parsePosition,
- Currency[] currency, boolean[] status) {
- int origPos = parsePosition.getIndex();
- if (!isReadyForParsing) {
- int savedCurrencySignCount = currencySignCount;
- setupCurrencyAffixForAllPatterns();
- // reset pattern back
- if (savedCurrencySignCount == CURRENCY_SIGN_COUNT_IN_PLURAL_FORMAT) {
- applyPatternWithoutExpandAffix(formatPattern, false);
- } else {
- applyPattern(formatPattern, false);
- }
- isReadyForParsing = true;
- }
- int maxPosIndex = origPos;
- int maxErrorPos = -1;
- boolean[] savedStatus = null;
- // First, parse against current pattern.
- // Since current pattern could be set by applyPattern(),
- // it could be an arbitrary pattern, and it may not be the one
- // defined in current locale.
- boolean[] tmpStatus = new boolean[STATUS_LENGTH];
- ParsePosition tmpPos = new ParsePosition(origPos);
- DigitList tmpDigitList = new DigitList();
- boolean found;
- if (style == NumberFormat.PLURALCURRENCYSTYLE) {
- found = subparse(text, tmpPos, tmpDigitList, tmpStatus, currency,
- negPrefixPattern, negSuffixPattern, posPrefixPattern, posSuffixPattern,
- true, Currency.LONG_NAME);
- } else {
- found = subparse(text, tmpPos, tmpDigitList, tmpStatus, currency,
- negPrefixPattern, negSuffixPattern, posPrefixPattern, posSuffixPattern,
- true, Currency.SYMBOL_NAME);
- }
- if (found) {
- if (tmpPos.getIndex() > maxPosIndex) {
- maxPosIndex = tmpPos.getIndex();
- savedStatus = tmpStatus;
- digitList = tmpDigitList;
- }
- } else {
- maxErrorPos = tmpPos.getErrorIndex();
- }
- // Then, parse against affix patterns. Those are currency patterns and currency
- // plural patterns defined in the locale.
- for (AffixForCurrency affix : affixPatternsForCurrency) {
- tmpStatus = new boolean[STATUS_LENGTH];
- tmpPos = new ParsePosition(origPos);
- tmpDigitList = new DigitList();
- boolean result = subparse(text, tmpPos, tmpDigitList, tmpStatus, currency,
- affix.getNegPrefix(), affix.getNegSuffix(),
- affix.getPosPrefix(), affix.getPosSuffix(),
- true, affix.getPatternType());
- if (result) {
- found = true;
- if (tmpPos.getIndex() > maxPosIndex) {
- maxPosIndex = tmpPos.getIndex();
- savedStatus = tmpStatus;
- digitList = tmpDigitList;
- }
- } else {
- maxErrorPos = (tmpPos.getErrorIndex() > maxErrorPos) ? tmpPos.getErrorIndex()
- : maxErrorPos;
- }
- }
- // Finally, parse against simple affix to find the match. For example, in
- // TestMonster suite, if the to-be-parsed text is "-\u00A40,00".
- // complexAffixCompare will not find match, since there is no ISO code matches
- // "\u00A4", and the parse stops at "\u00A4". We will just use simple affix
- // comparison (look for exact match) to pass it.
- //
- // TODO: We should parse against simple affix first when
- // output currency is not requested. After the complex currency
- // parsing implementation was introduced, the default currency
- // instance parsing slowed down because of the new code flow.
- // I filed #10312 - Yoshito
- tmpStatus = new boolean[STATUS_LENGTH];
- tmpPos = new ParsePosition(origPos);
- tmpDigitList = new DigitList();
-
- // Disable complex currency parsing and try it again.
- boolean result = subparse(text, tmpPos, tmpDigitList, tmpStatus, currency,
- negativePrefix, negativeSuffix, positivePrefix, positiveSuffix,
- false /* disable complex currency parsing */, Currency.SYMBOL_NAME);
- if (result) {
- if (tmpPos.getIndex() > maxPosIndex) {
- maxPosIndex = tmpPos.getIndex();
- savedStatus = tmpStatus;
- digitList = tmpDigitList;
- }
- found = true;
- } else {
- maxErrorPos = (tmpPos.getErrorIndex() > maxErrorPos) ? tmpPos.getErrorIndex() :
- maxErrorPos;
- }
-
- if (!found) {
- // parsePosition.setIndex(origPos);
- parsePosition.setErrorIndex(maxErrorPos);
- } else {
- parsePosition.setIndex(maxPosIndex);
- parsePosition.setErrorIndex(-1);
- for (int index = 0; index < STATUS_LENGTH; ++index) {
- status[index] = savedStatus[index];
- }
- }
- return found;
- }
-
- // Get affix patterns used in locale's currency pattern (NumberPatterns[1]) and
- // currency plural pattern (CurrencyUnitPatterns).
- private void setupCurrencyAffixForAllPatterns() {
- if (currencyPluralInfo == null) {
- currencyPluralInfo = new CurrencyPluralInfo(symbols.getULocale());
- }
- affixPatternsForCurrency = new HashSet<AffixForCurrency>();
-
- // save the current pattern, since it will be changed by
- // applyPatternWithoutExpandAffix
- String savedFormatPattern = formatPattern;
-
- // CURRENCYSTYLE and ISOCURRENCYSTYLE should have the same prefix and suffix, so,
- // only need to save one of them. Here, chose onlyApplyPatternWithoutExpandAffix
- // without saving the actualy pattern in 'pattern' data member. TODO: is it uloc?
- applyPatternWithoutExpandAffix(getPattern(symbols.getULocale(), NumberFormat.CURRENCYSTYLE),
- false);
- AffixForCurrency affixes = new AffixForCurrency(
- negPrefixPattern, negSuffixPattern, posPrefixPattern, posSuffixPattern,
- Currency.SYMBOL_NAME);
- affixPatternsForCurrency.add(affixes);
-
- // add plural pattern
- Iterator<String> iter = currencyPluralInfo.pluralPatternIterator();
- Set<String> currencyUnitPatternSet = new HashSet<String>();
- while (iter.hasNext()) {
- String pluralCount = iter.next();
- String currencyPattern = currencyPluralInfo.getCurrencyPluralPattern(pluralCount);
- if (currencyPattern != null &&
- currencyUnitPatternSet.contains(currencyPattern) == false) {
- currencyUnitPatternSet.add(currencyPattern);
- applyPatternWithoutExpandAffix(currencyPattern, false);
- affixes = new AffixForCurrency(negPrefixPattern, negSuffixPattern, posPrefixPattern,
- posSuffixPattern, Currency.LONG_NAME);
- affixPatternsForCurrency.add(affixes);
- }
- }
- // reset pattern back
- formatPattern = savedFormatPattern;
- }
-
- // currency formatting style options
- private static final int CURRENCY_SIGN_COUNT_ZERO = 0;
- private static final int CURRENCY_SIGN_COUNT_IN_SYMBOL_FORMAT = 1;
- private static final int CURRENCY_SIGN_COUNT_IN_ISO_FORMAT = 2;
- private static final int CURRENCY_SIGN_COUNT_IN_PLURAL_FORMAT = 3;
-
- private static final int STATUS_INFINITE = 0;
- private static final int STATUS_POSITIVE = 1;
- private static final int STATUS_UNDERFLOW = 2;
- private static final int STATUS_LENGTH = 3;
-
- private static final UnicodeSet dotEquivalents = new UnicodeSet(
- //"[.\u2024\u3002\uFE12\uFE52\uFF0E\uFF61]"
- 0x002E, 0x002E,
- 0x2024, 0x2024,
- 0x3002, 0x3002,
- 0xFE12, 0xFE12,
- 0xFE52, 0xFE52,
- 0xFF0E, 0xFF0E,
- 0xFF61, 0xFF61).freeze();
-
- private static final UnicodeSet commaEquivalents = new UnicodeSet(
- //"[,\u060C\u066B\u3001\uFE10\uFE11\uFE50\uFE51\uFF0C\uFF64]"
- 0x002C, 0x002C,
- 0x060C, 0x060C,
- 0x066B, 0x066B,
- 0x3001, 0x3001,
- 0xFE10, 0xFE11,
- 0xFE50, 0xFE51,
- 0xFF0C, 0xFF0C,
- 0xFF64, 0xFF64).freeze();
-
-// private static final UnicodeSet otherGroupingSeparators = new UnicodeSet(
-// //"[\\ '\u00A0\u066C\u2000-\u200A\u2018\u2019\u202F\u205F\u3000\uFF07]"
-// 0x0020, 0x0020,
-// 0x0027, 0x0027,
-// 0x00A0, 0x00A0,
-// 0x066C, 0x066C,
-// 0x2000, 0x200A,
-// 0x2018, 0x2019,
-// 0x202F, 0x202F,
-// 0x205F, 0x205F,
-// 0x3000, 0x3000,
-// 0xFF07, 0xFF07).freeze();
-
- private static final UnicodeSet strictDotEquivalents = new UnicodeSet(
- //"[.\u2024\uFE52\uFF0E\uFF61]"
- 0x002E, 0x002E,
- 0x2024, 0x2024,
- 0xFE52, 0xFE52,
- 0xFF0E, 0xFF0E,
- 0xFF61, 0xFF61).freeze();
-
- private static final UnicodeSet strictCommaEquivalents = new UnicodeSet(
- //"[,\u066B\uFE10\uFE50\uFF0C]"
- 0x002C, 0x002C,
- 0x066B, 0x066B,
- 0xFE10, 0xFE10,
- 0xFE50, 0xFE50,
- 0xFF0C, 0xFF0C).freeze();
-
-// private static final UnicodeSet strictOtherGroupingSeparators = new UnicodeSet(
-// //"[\\ '\u00A0\u066C\u2000-\u200A\u2018\u2019\u202F\u205F\u3000\uFF07]"
-// 0x0020, 0x0020,
-// 0x0027, 0x0027,
-// 0x00A0, 0x00A0,
-// 0x066C, 0x066C,
-// 0x2000, 0x200A,
-// 0x2018, 0x2019,
-// 0x202F, 0x202F,
-// 0x205F, 0x205F,
-// 0x3000, 0x3000,
-// 0xFF07, 0xFF07).freeze();
-
- private static final UnicodeSet defaultGroupingSeparators =
- // new UnicodeSet(dotEquivalents).addAll(commaEquivalents)
- // .addAll(otherGroupingSeparators).freeze();
- new UnicodeSet(
- 0x0020, 0x0020,
- 0x0027, 0x0027,
- 0x002C, 0x002C,
- 0x002E, 0x002E,
- 0x00A0, 0x00A0,
- 0x060C, 0x060C,
- 0x066B, 0x066C,
- 0x2000, 0x200A,
- 0x2018, 0x2019,
- 0x2024, 0x2024,
- 0x202F, 0x202F,
- 0x205F, 0x205F,
- 0x3000, 0x3002,
- 0xFE10, 0xFE12,
- 0xFE50, 0xFE52,
- 0xFF07, 0xFF07,
- 0xFF0C, 0xFF0C,
- 0xFF0E, 0xFF0E,
- 0xFF61, 0xFF61,
- 0xFF64, 0xFF64).freeze();
-
- private static final UnicodeSet strictDefaultGroupingSeparators =
- // new UnicodeSet(strictDotEquivalents).addAll(strictCommaEquivalents)
- // .addAll(strictOtherGroupingSeparators).freeze();
- new UnicodeSet(
- 0x0020, 0x0020,
- 0x0027, 0x0027,
- 0x002C, 0x002C,
- 0x002E, 0x002E,
- 0x00A0, 0x00A0,
- 0x066B, 0x066C,
- 0x2000, 0x200A,
- 0x2018, 0x2019,
- 0x2024, 0x2024,
- 0x202F, 0x202F,
- 0x205F, 0x205F,
- 0x3000, 0x3000,
- 0xFE10, 0xFE10,
- 0xFE50, 0xFE50,
- 0xFE52, 0xFE52,
- 0xFF07, 0xFF07,
- 0xFF0C, 0xFF0C,
- 0xFF0E, 0xFF0E,
- 0xFF61, 0xFF61).freeze();
-
- static final UnicodeSet minusSigns =
- new UnicodeSet(
- 0x002D, 0x002D,
- 0x207B, 0x207B,
- 0x208B, 0x208B,
- 0x2212, 0x2212,
- 0x2796, 0x2796,
- 0xFE63, 0xFE63,
- 0xFF0D, 0xFF0D).freeze();
-
- static final UnicodeSet plusSigns =
- new UnicodeSet(
- 0x002B, 0x002B,
- 0x207A, 0x207A,
- 0x208A, 0x208A,
- 0x2795, 0x2795,
- 0xFB29, 0xFB29,
- 0xFE62, 0xFE62,
- 0xFF0B, 0xFF0B).freeze();
-
- // equivalent grouping and decimal support
- static final boolean skipExtendedSeparatorParsing = ICUConfig.get(
- "com.ibm.icu.text.DecimalFormat.SkipExtendedSeparatorParsing", "false")
- .equals("true");
-
- // allow control of requiring a matching decimal point when parsing
- boolean parseRequireDecimalPoint = false;
-
- // When parsing a number with big exponential value, it requires to transform the
- // value into a string representation to construct BigInteger instance. We want to
- // set the maximum size because it can easily trigger OutOfMemoryException.
- // PARSE_MAX_EXPONENT is currently set to 1000 (See getParseMaxDigits()),
- // which is much bigger than MAX_VALUE of Double ( See the problem reported by ticket#5698
- private int PARSE_MAX_EXPONENT = 1000;
-
- /**
- * Parses the given text into a number. The text is parsed beginning at parsePosition,
- * until an unparseable character is seen.
- *
- * @param text the string to parse.
- * @param parsePosition the position at which to being parsing. Upon return, the first
- * unparseable character.
- * @param digits the DigitList to set to the parsed value.
- * @param status Upon return contains boolean status flags indicating whether the
- * value was infinite and whether it was positive.
- * @param currency return value for parsed currency, for generic currency parsing
- * mode, or null for normal parsing. In generic currency parsing mode, any currency is
- * parsed, not just the currency that this formatter is set to.
- * @param negPrefix negative prefix pattern
- * @param negSuffix negative suffix pattern
- * @param posPrefix positive prefix pattern
- * @param negSuffix negative suffix pattern
- * @param parseComplexCurrency whether it is complex currency parsing or not.
- * @param type type of currency to parse against, LONG_NAME only or not.
- */
- private final boolean subparse(
- String text, ParsePosition parsePosition, DigitList digits,
- boolean status[], Currency currency[], String negPrefix, String negSuffix, String posPrefix,
- String posSuffix, boolean parseComplexCurrency, int type) {
-
- int position = parsePosition.getIndex();
- int oldStart = parsePosition.getIndex();
-
- // Match padding before prefix
- if (formatWidth > 0 && padPosition == PAD_BEFORE_PREFIX) {
- position = skipPadding(text, position);
- }
-
- // Match positive and negative prefixes; prefer longest match.
- int posMatch = compareAffix(text, position, false, true, posPrefix, parseComplexCurrency, type, currency);
- int negMatch = compareAffix(text, position, true, true, negPrefix, parseComplexCurrency, type, currency);
- if (posMatch >= 0 && negMatch >= 0) {
- if (posMatch > negMatch) {
- negMatch = -1;
- } else if (negMatch > posMatch) {
- posMatch = -1;
- }
- }
- if (posMatch >= 0) {
- position += posMatch;
- } else if (negMatch >= 0) {
- position += negMatch;
- } else {
- parsePosition.setErrorIndex(position);
- return false;
- }
-
- // Match padding after prefix
- if (formatWidth > 0 && padPosition == PAD_AFTER_PREFIX) {
- position = skipPadding(text, position);
- }
-
- // process digits or Inf, find decimal position
- status[STATUS_INFINITE] = false;
- if (text.regionMatches(position, symbols.getInfinity(), 0,
- symbols.getInfinity().length())) {
- position += symbols.getInfinity().length();
- status[STATUS_INFINITE] = true;
- } else {
- // We now have a string of digits, possibly with grouping symbols, and decimal
- // points. We want to process these into a DigitList. We don't want to put a
- // bunch of leading zeros into the DigitList though, so we keep track of the
- // location of the decimal point, put only significant digits into the
- // DigitList, and adjust the exponent as needed.
-
- digits.decimalAt = digits.count = 0;
- String decimal = (currencySignCount == CURRENCY_SIGN_COUNT_ZERO) ?
- symbols.getDecimalSeparatorString() : symbols.getMonetaryDecimalSeparatorString();
- String grouping = (currencySignCount == CURRENCY_SIGN_COUNT_ZERO) ?
- symbols.getGroupingSeparatorString() : symbols.getMonetaryGroupingSeparatorString();
-
- String exponentSep = symbols.getExponentSeparator();
- boolean sawDecimal = false;
- boolean sawGrouping = false;
- boolean sawDigit = false;
- long exponent = 0; // Set to the exponent value, if any
-
- // strict parsing
- boolean strictParse = isParseStrict();
- boolean strictFail = false; // did we exit with a strict parse failure?
- int lastGroup = -1; // where did we last see a grouping separator?
- int groupedDigitCount = 0; // tracking count of digits delimited by grouping separator
- int gs2 = groupingSize2 == 0 ? groupingSize : groupingSize2;
-
- UnicodeSet decimalEquiv = skipExtendedSeparatorParsing ? UnicodeSet.EMPTY :
- getEquivalentDecimals(decimal, strictParse);
- UnicodeSet groupEquiv = skipExtendedSeparatorParsing ? UnicodeSet.EMPTY :
- (strictParse ? strictDefaultGroupingSeparators : defaultGroupingSeparators);
-
- // We have to track digitCount ourselves, because digits.count will pin when
- // the maximum allowable digits is reached.
- int digitCount = 0;
-
- int backup = -1; // used for preserving the last confirmed position
- int[] parsedDigit = {-1}; // allocates int[1] for parsing a single digit
-
- while (position < text.length()) {
- // Check if the sequence at the current position matches a decimal digit
- int matchLen = matchesDigit(text, position, parsedDigit);
- if (matchLen > 0) {
- // matched a digit
- // Cancel out backup setting (see grouping handler below)
- if (backup != -1) {
- if (strictParse) {
- // comma followed by digit, so group before comma is a secondary
- // group. If there was a group separator before that, the group
- // must == the secondary group length, else it can be <= the the
- // secondary group length.
- if ((lastGroup != -1 && groupedDigitCount != gs2)
- || (lastGroup == -1 && groupedDigitCount > gs2)) {
- strictFail = true;
- break;
- }
- }
- lastGroup = backup;
- groupedDigitCount = 0;
- }
-
- groupedDigitCount++;
- position += matchLen;
- backup = -1;
- sawDigit = true;
- if (parsedDigit[0] == 0 && digits.count == 0) {
- // Handle leading zeros
- if (!sawDecimal) {
- // Ignore leading zeros in integer part of number.
- continue;
- }
- // If we have seen the decimal, but no significant digits yet,
- // then we account for leading zeros by decrementing the
- // digits.decimalAt into negative values.
- --digits.decimalAt;
- } else {
- ++digitCount;
- digits.append((char) (parsedDigit[0] + '0'));
- }
- continue;
- }
-
- // Check if the sequence at the current position matches locale's decimal separator
- int decimalStrLen = decimal.length();
- if (text.regionMatches(position, decimal, 0, decimalStrLen)) {
- // matched a decimal separator
- if (strictParse) {
- if (backup != -1 ||
- (lastGroup != -1 && groupedDigitCount != groupingSize)) {
- strictFail = true;
- break;
- }
- }
-
- // If we're only parsing integers, or if we ALREADY saw the decimal,
- // then don't parse this one.
- if (isParseIntegerOnly() || sawDecimal) {
- break;
- }
-
- digits.decimalAt = digitCount; // Not digits.count!
- sawDecimal = true;
- position += decimalStrLen;
- continue;
- }
-
- if (isGroupingUsed()) {
- // Check if the sequence at the current position matches locale's grouping separator
- int groupingStrLen = grouping.length();
- if (text.regionMatches(position, grouping, 0, groupingStrLen)) {
- if (sawDecimal) {
- break;
- }
-
- if (strictParse) {
- if ((!sawDigit || backup != -1)) {
- // leading group, or two group separators in a row
- strictFail = true;
- break;
- }
- }
-
- // Ignore grouping characters, if we are using them, but require that
- // they be followed by a digit. Otherwise we backup and reprocess
- // them.
- backup = position;
- position += groupingStrLen;
- sawGrouping = true;
- continue;
- }
- }
-
- // Check if the code point at the current position matches one of decimal/grouping equivalent group chars
- int cp = text.codePointAt(position);
- if (!sawDecimal && decimalEquiv.contains(cp)) {
- // matched a decimal separator
- if (strictParse) {
- if (backup != -1 ||
- (lastGroup != -1 && groupedDigitCount != groupingSize)) {
- strictFail = true;
- break;
- }
- }
-
- // If we're only parsing integers, or if we ALREADY saw the decimal,
- // then don't parse this one.
- if (isParseIntegerOnly()) {
- break;
- }
-
- digits.decimalAt = digitCount; // Not digits.count!
-
- // Once we see a decimal separator character, we only accept that
- // decimal separator character from then on.
- decimal = String.valueOf(Character.toChars(cp));
-
- sawDecimal = true;
- position += Character.charCount(cp);
- continue;
- }
-
- if (isGroupingUsed() && !sawGrouping && groupEquiv.contains(cp)) {
- // matched a grouping separator
- if (sawDecimal) {
- break;
- }
-
- if (strictParse) {
- if ((!sawDigit || backup != -1)) {
- // leading group, or two group separators in a row
- strictFail = true;
- break;
- }
- }
-
- // Once we see a grouping character, we only accept that grouping
- // character from then on.
- grouping = String.valueOf(Character.toChars(cp));
-
- // Ignore grouping characters, if we are using them, but require that
- // they be followed by a digit. Otherwise we backup and reprocess
- // them.
- backup = position;
- position += Character.charCount(cp);
- sawGrouping = true;
- continue;
- }
-
- // Check if the sequence at the current position matches locale's exponent separator
- int exponentSepStrLen = exponentSep.length();
- if (text.regionMatches(true, position, exponentSep, 0, exponentSepStrLen)) {
- // parse sign, if present
- boolean negExp = false;
- int pos = position + exponentSep.length();
- if (pos < text.length()) {
- String plusSign = symbols.getPlusSignString();
- String minusSign = symbols.getMinusSignString();
- if (text.regionMatches(pos, plusSign, 0, plusSign.length())) {
- pos += plusSign.length();
- } else if (text.regionMatches(pos, minusSign, 0, minusSign.length())) {
- pos += minusSign.length();
- negExp = true;
- }
- }
-
- DigitList exponentDigits = new DigitList();
- exponentDigits.count = 0;
- while (pos < text.length()) {
- int digitMatchLen = matchesDigit(text, pos, parsedDigit);
- if (digitMatchLen > 0) {
- exponentDigits.append((char) (parsedDigit[0] + '0'));
- pos += digitMatchLen;
- } else {
- break;
- }
- }
-
- if (exponentDigits.count > 0) {
- // defer strict parse until we know we have a bona-fide exponent
- if (strictParse && sawGrouping) {
- strictFail = true;
- break;
- }
-
- // Quick overflow check for exponential part. Actual limit check
- // will be done later in this code.
- if (exponentDigits.count > 10 /* maximum decimal digits for int */) {
- if (negExp) {
- // set underflow flag
- status[STATUS_UNDERFLOW] = true;
- } else {
- // set infinite flag
- status[STATUS_INFINITE] = true;
- }
- } else {
- exponentDigits.decimalAt = exponentDigits.count;
- exponent = exponentDigits.getLong();
- if (negExp) {
- exponent = -exponent;
- }
- }
- position = pos; // Advance past the exponent
- }
-
- break; // Whether we fail or succeed, we exit this loop
- }
-
- // All other cases, stop parsing
- break;
- }
-
- if (digits.decimalAt == 0 && isDecimalPatternMatchRequired()) {
- if (this.formatPattern.indexOf(decimal) != -1) {
- parsePosition.setIndex(oldStart);
- parsePosition.setErrorIndex(position);
- return false;
- }
- }
-
- if (backup != -1)
- position = backup;
-
- // If there was no decimal point we have an integer
- if (!sawDecimal) {
- digits.decimalAt = digitCount; // Not digits.count!
- }
-
- // check for strict parse errors
- if (strictParse && !sawDecimal) {
- if (lastGroup != -1 && groupedDigitCount != groupingSize) {
- strictFail = true;
- }
- }
- if (strictFail) {
- // only set with strictParse and a leading zero error leading zeros are an
- // error with strict parsing except immediately before nondigit (except
- // group separator followed by digit), or end of text.
-
- parsePosition.setIndex(oldStart);
- parsePosition.setErrorIndex(position);
- return false;
- }
-
- // Adjust for exponent, if any
- exponent += digits.decimalAt;
- if (exponent < -getParseMaxDigits()) {
- status[STATUS_UNDERFLOW] = true;
- } else if (exponent > getParseMaxDigits()) {
- status[STATUS_INFINITE] = true;
- } else {
- digits.decimalAt = (int) exponent;
- }
-
- // If none of the text string was recognized. For example, parse "x" with
- // pattern "#0.00" (return index and error index both 0) parse "$" with
- // pattern "$#0.00". (return index 0 and error index 1).
- if (!sawDigit && digitCount == 0) {
- parsePosition.setIndex(oldStart);
- parsePosition.setErrorIndex(oldStart);
- return false;
- }
- }
-
- // Match padding before suffix
- if (formatWidth > 0 && padPosition == PAD_BEFORE_SUFFIX) {
- position = skipPadding(text, position);
- }
-
- // Match positive and negative suffixes; prefer longest match.
- if (posMatch >= 0) {
- posMatch = compareAffix(text, position, false, false, posSuffix, parseComplexCurrency, type, currency);
- }
- if (negMatch >= 0) {
- negMatch = compareAffix(text, position, true, false, negSuffix, parseComplexCurrency, type, currency);
- }
- if (posMatch >= 0 && negMatch >= 0) {
- if (posMatch > negMatch) {
- negMatch = -1;
- } else if (negMatch > posMatch) {
- posMatch = -1;
- }
- }
-
- // Fail if neither or both
- if ((posMatch >= 0) == (negMatch >= 0)) {
- parsePosition.setErrorIndex(position);
- return false;
- }
-
- position += (posMatch >= 0 ? posMatch : negMatch);
-
- // Match padding after suffix
- if (formatWidth > 0 && padPosition == PAD_AFTER_SUFFIX) {
- position = skipPadding(text, position);
- }
-
- parsePosition.setIndex(position);
-
- status[STATUS_POSITIVE] = (posMatch >= 0);
-
- if (parsePosition.getIndex() == oldStart) {
- parsePosition.setErrorIndex(position);
- return false;
- }
- return true;
- }
-
- /**
- * Check if the substring at the specified position matches a decimal digit.
- * If matched, this method sets the decimal value to <code>decVal</code> and
- * returns matched length.
- *
- * @param str The input string
- * @param start The start index
- * @param decVal Receives decimal value
- * @return Length of match, or 0 if the sequence at the position is not
- * a decimal digit.
- */
- private int matchesDigit(String str, int start, int[] decVal) {
- String[] localeDigits = symbols.getDigitStringsLocal();
-
- // Check if the sequence at the current position matches locale digits.
- for (int i = 0; i < 10; i++) {
- int digitStrLen = localeDigits[i].length();
- if (str.regionMatches(start, localeDigits[i], 0, digitStrLen)) {
- decVal[0] = i;
- return digitStrLen;
- }
- }
-
- // If no locale digit match, then check if this is a Unicode digit
- int cp = str.codePointAt(start);
- decVal[0] = UCharacter.digit(cp, 10);
- if (decVal[0] >= 0) {
- return Character.charCount(cp);
- }
-
- return 0;
- }
-
- /**
- * Returns a set of characters equivalent to the given desimal separator used for
- * parsing number. This method may return an empty set.
- */
- private UnicodeSet getEquivalentDecimals(String decimal, boolean strictParse) {
- UnicodeSet equivSet = UnicodeSet.EMPTY;
- if (strictParse) {
- if (strictDotEquivalents.contains(decimal)) {
- equivSet = strictDotEquivalents;
- } else if (strictCommaEquivalents.contains(decimal)) {
- equivSet = strictCommaEquivalents;
- }
- } else {
- if (dotEquivalents.contains(decimal)) {
- equivSet = dotEquivalents;
- } else if (commaEquivalents.contains(decimal)) {
- equivSet = commaEquivalents;
- }
- }
- return equivSet;
- }
-
- /**
- * Starting at position, advance past a run of pad characters, if any. Return the
- * index of the first character after position that is not a pad character. Result is
- * >= position.
- */
- private final int skipPadding(String text, int position) {
- while (position < text.length() && text.charAt(position) == pad) {
- ++position;
- }
- return position;
- }
-
- /**
- * Returns the length matched by the given affix, or -1 if none. Runs of white space
- * in the affix, match runs of white space in the input. Pattern white space and input
- * white space are determined differently; see code.
- *
- * @param text input text
- * @param pos offset into input at which to begin matching
- * @param isNegative
- * @param isPrefix
- * @param affixPat affix pattern used for currency affix comparison
- * @param complexCurrencyParsing whether it is currency parsing or not
- * @param type compare against currency type, LONG_NAME only or not.
- * @param currency return value for parsed currency, for generic currency parsing
- * mode, or null for normal parsing. In generic currency parsing mode, any currency
- * is parsed, not just the currency that this formatter is set to.
- * @return length of input that matches, or -1 if match failure
- */
- private int compareAffix(String text, int pos, boolean isNegative, boolean isPrefix,
- String affixPat, boolean complexCurrencyParsing, int type, Currency[] currency) {
- if (currency != null || currencyChoice != null || (currencySignCount != CURRENCY_SIGN_COUNT_ZERO && complexCurrencyParsing)) {
- return compareComplexAffix(affixPat, text, pos, type, currency);
- }
- if (isPrefix) {
- return compareSimpleAffix(isNegative ? negativePrefix : positivePrefix, text, pos);
- } else {
- return compareSimpleAffix(isNegative ? negativeSuffix : positiveSuffix, text, pos);
- }
-
- }
-
- /**
- * Check for bidi marks: LRM, RLM, ALM
- */
- private static boolean isBidiMark(int c) {
- return (c==0x200E || c==0x200F || c==0x061C);
- }
-
- /**
- * Remove bidi marks from affix
- */
- private static String trimMarksFromAffix(String affix) {
- boolean hasBidiMark = false;
- int idx = 0;
- for (; idx < affix.length(); idx++) {
- if (isBidiMark(affix.charAt(idx))) {
- hasBidiMark = true;
- break;
- }
- }
- if (!hasBidiMark) {
- return affix;
- }
-
- StringBuilder buf = new StringBuilder();
- buf.append(affix, 0, idx);
- idx++; // skip the first Bidi mark
- for (; idx < affix.length(); idx++) {
- char c = affix.charAt(idx);
- if (!isBidiMark(c)) {
- buf.append(c);
- }
- }
-
- return buf.toString();
- }
-
- /**
- * Return the length matched by the given affix, or -1 if none. Runs of white space in
- * the affix, match runs of white space in the input. Pattern white space and input
- * white space are determined differently; see code.
- *
- * @param affix pattern string, taken as a literal
- * @param input input text
- * @param pos offset into input at which to begin matching
- * @return length of input that matches, or -1 if match failure
- */
- private static int compareSimpleAffix(String affix, String input, int pos) {
- int start = pos;
- // Affixes here might consist of sign, currency symbol and related spacing, etc.
- // For more efficiency we should keep lazily-created trimmed affixes around in
- // instance variables instead of trimming each time they are used (the next step).
- String trimmedAffix = (affix.length() > 1)? trimMarksFromAffix(affix): affix;
- for (int i = 0; i < trimmedAffix.length();) {
- int c = UTF16.charAt(trimmedAffix, i);
- int len = UTF16.getCharCount(c);
- if (PatternProps.isWhiteSpace(c)) {
- // We may have a pattern like: \u200F and input text like: \u200F Note
- // that U+200F and U+0020 are Pattern_White_Space but only U+0020 is
- // UWhiteSpace. So we have to first do a direct match of the run of RULE
- // whitespace in the pattern, then match any extra characters.
- boolean literalMatch = false;
- while (pos < input.length()) {
- int ic = UTF16.charAt(input, pos);
- if (ic == c) {
- literalMatch = true;
- i += len;
- pos += len;
- if (i == trimmedAffix.length()) {
- break;
- }
- c = UTF16.charAt(trimmedAffix, i);
- len = UTF16.getCharCount(c);
- if (!PatternProps.isWhiteSpace(c)) {
- break;
- }
- } else if (isBidiMark(ic)) {
- pos++; // just skip over this input text
- } else {
- break;
- }
- }
-
- // Advance over run in trimmedAffix
- i = skipPatternWhiteSpace(trimmedAffix, i);
-
- // Advance over run in input text. Must see at least one white space char
- // in input, unless we've already matched some characters literally.
- int s = pos;
- pos = skipUWhiteSpace(input, pos);
- if (pos == s && !literalMatch) {
- return -1;
- }
- // If we skip UWhiteSpace in the input text, we need to skip it in the
- // pattern. Otherwise, the previous lines may have skipped over text
- // (such as U+00A0) that is also in the trimmedAffix.
- i = skipUWhiteSpace(trimmedAffix, i);
- } else {
- boolean match = false;
- while (pos < input.length()) {
- int ic = UTF16.charAt(input, pos);
- if (!match && equalWithSignCompatibility(ic, c)) {
- i += len;
- pos += len;
- match = true;
- } else if (isBidiMark(ic)) {
- pos++; // just skip over this input text
- } else {
- break;
- }
- }
- if (!match) {
- return -1;
- }
- }
- }
- return pos - start;
- }
-
- private static boolean equalWithSignCompatibility(int lhs, int rhs) {
- return lhs == rhs
- || (minusSigns.contains(lhs) && minusSigns.contains(rhs))
- || (plusSigns.contains(lhs) && plusSigns.contains(rhs));
- }
-
- /**
- * Skips over a run of zero or more Pattern_White_Space characters at pos in text.
- */
- private static int skipPatternWhiteSpace(String text, int pos) {
- while (pos < text.length()) {
- int c = UTF16.charAt(text, pos);
- if (!PatternProps.isWhiteSpace(c)) {
- break;
- }
- pos += UTF16.getCharCount(c);
- }
- return pos;
- }
-
- /**
- * Skips over a run of zero or more isUWhiteSpace() characters at pos in text.
- */
- private static int skipUWhiteSpace(String text, int pos) {
- while (pos < text.length()) {
- int c = UTF16.charAt(text, pos);
- if (!UCharacter.isUWhiteSpace(c)) {
- break;
- }
- pos += UTF16.getCharCount(c);
- }
- return pos;
- }
-
- /**
- * Skips over a run of zero or more bidi marks at pos in text.
- */
- private static int skipBidiMarks(String text, int pos) {
- while (pos < text.length()) {
- int c = UTF16.charAt(text, pos);
- if (!isBidiMark(c)) {
- break;
- }
- pos += UTF16.getCharCount(c);
- }
- return pos;
- }
-
- /**
- * Returns the length matched by the given affix, or -1 if none.
- *
- * @param affixPat pattern string
- * @param text input text
- * @param pos offset into input at which to begin matching
- * @param type parse against currency type, LONG_NAME only or not.
- * @param currency return value for parsed currency, for generic
- * currency parsing mode, or null for normal parsing. In generic
- * currency parsing mode, any currency is parsed, not just the
- * currency that this formatter is set to.
- * @return position after the matched text, or -1 if match failure
- */
- private int compareComplexAffix(String affixPat, String text, int pos, int type,
- Currency[] currency) {
- int start = pos;
- for (int i = 0; i < affixPat.length() && pos >= 0;) {
- char c = affixPat.charAt(i++);
- if (c == QUOTE) {
- for (;;) {
- int j = affixPat.indexOf(QUOTE, i);
- if (j == i) {
- pos = match(text, pos, QUOTE);
- i = j + 1;
- break;
- } else if (j > i) {
- pos = match(text, pos, affixPat.substring(i, j));
- i = j + 1;
- if (i < affixPat.length() && affixPat.charAt(i) == QUOTE) {
- pos = match(text, pos, QUOTE);
- ++i;
- // loop again
- } else {
- break;
- }
- } else {
- // Unterminated quote; should be caught by apply
- // pattern.
- throw new RuntimeException();
- }
- }
- continue;
- }
-
- String affix = null;
-
- switch (c) {
- case CURRENCY_SIGN:
- // since the currency names in choice format is saved the same way as
- // other currency names, do not need to do currency choice parsing here.
- // the general currency parsing parse against all names, including names
- // in choice format. assert(currency != null || (getCurrency() != null &&
- // currencyChoice != null));
- boolean intl = i < affixPat.length() && affixPat.charAt(i) == CURRENCY_SIGN;
- if (intl) {
- ++i;
- }
- boolean plural = i < affixPat.length() && affixPat.charAt(i) == CURRENCY_SIGN;
- if (plural) {
- ++i;
- intl = false;
- }
- // Parse generic currency -- anything for which we have a display name, or
- // any 3-letter ISO code. Try to parse display name for our locale; first
- // determine our locale. TODO: use locale in CurrencyPluralInfo
- ULocale uloc = getLocale(ULocale.VALID_LOCALE);
- if (uloc == null) {
- // applyPattern has been called; use the symbols
- uloc = symbols.getLocale(ULocale.VALID_LOCALE);
- }
- // Delegate parse of display name => ISO code to Currency
- ParsePosition ppos = new ParsePosition(pos);
- // using Currency.parse to handle mixed style parsing.
- String iso = Currency.parse(uloc, text, type, ppos);
-
- // If parse succeeds, populate currency[0]
- if (iso != null) {
- if (currency != null) {
- currency[0] = Currency.getInstance(iso);
- } else {
- // The formatter is currency-style but the client has not requested
- // the value of the parsed currency. In this case, if that value does
- // not match the formatter's current value, then the parse fails.
- Currency effectiveCurr = getEffectiveCurrency();
- if (iso.compareTo(effectiveCurr.getCurrencyCode()) != 0) {
- pos = -1;
- continue;
- }
- }
- pos = ppos.getIndex();
- } else {
- pos = -1;
- }
- continue;
- case PATTERN_PERCENT:
- affix = symbols.getPercentString();
- break;
- case PATTERN_PER_MILLE:
- affix = symbols.getPerMillString();
- break;
- case PATTERN_PLUS_SIGN:
- affix = symbols.getPlusSignString();
- break;
- case PATTERN_MINUS_SIGN:
- affix = symbols.getMinusSignString();
- break;
- default:
- // fall through to affix != null test, which will fail
- break;
- }
-
- if (affix != null) {
- pos = match(text, pos, affix);
- continue;
- }
-
- pos = match(text, pos, c);
- if (PatternProps.isWhiteSpace(c)) {
- i = skipPatternWhiteSpace(affixPat, i);
- }
- }
-
- return pos - start;
- }
-
- /**
- * Matches a single character at text[pos] and return the index of the next character
- * upon success. Return -1 on failure. If ch is a Pattern_White_Space then match a run of
- * white space in text.
- */
- static final int match(String text, int pos, int ch) {
- if (pos < 0 || pos >= text.length()) {
- return -1;
- }
- pos = skipBidiMarks(text, pos);
- if (PatternProps.isWhiteSpace(ch)) {
- // Advance over run of white space in input text
- // Must see at least one white space char in input
- int s = pos;
- pos = skipPatternWhiteSpace(text, pos);
- if (pos == s) {
- return -1;
- }
- return pos;
- }
- if (pos >= text.length() || UTF16.charAt(text, pos) != ch) {
- return -1;
- }
- pos = skipBidiMarks(text, pos + UTF16.getCharCount(ch));
- return pos;
- }
-
- /**
- * Matches a string at text[pos] and return the index of the next character upon
- * success. Return -1 on failure. Match a run of white space in str with a run of
- * white space in text.
- */
- static final int match(String text, int pos, String str) {
- for (int i = 0; i < str.length() && pos >= 0;) {
- int ch = UTF16.charAt(str, i);
- i += UTF16.getCharCount(ch);
- if (isBidiMark(ch)) {
- continue;
- }
- pos = match(text, pos, ch);
- if (PatternProps.isWhiteSpace(ch)) {
- i = skipPatternWhiteSpace(str, i);
- }
- }
- return pos;
- }
-
- /**
- * Returns a copy of the decimal format symbols used by this format.
- *
- * @return desired DecimalFormatSymbols
- * @see DecimalFormatSymbols
- * @stable ICU 2.0
- */
- public DecimalFormatSymbols getDecimalFormatSymbols() {
- try {
- // don't allow multiple references
- return (DecimalFormatSymbols) symbols.clone();
- } catch (Exception foo) {
- return null; // should never happen
- }
- }
-
- /**
- * Sets the decimal format symbols used by this format. The format uses a copy of the
- * provided symbols.
- *
- * @param newSymbols desired DecimalFormatSymbols
- * @see DecimalFormatSymbols
- * @stable ICU 2.0
- */
- public void setDecimalFormatSymbols(DecimalFormatSymbols newSymbols) {
- symbols = (DecimalFormatSymbols) newSymbols.clone();
- setCurrencyForSymbols();
- expandAffixes(null);
- }
-
- /**
- * Update the currency object to match the symbols. This method is used only when the
- * caller has passed in a symbols object that may not be the default object for its
- * locale.
- */
- private void setCurrencyForSymbols() {
-
- // Bug 4212072 Update the affix strings according to symbols in order to keep the
- // affix strings up to date. [Richard/GCL]
-
- // With the introduction of the Currency object, the currency symbols in the DFS
- // object are ignored. For backward compatibility, we check any explicitly set DFS
- // object. If it is a default symbols object for its locale, we change the
- // currency object to one for that locale. If it is custom, we set the currency to
- // null.
- DecimalFormatSymbols def = new DecimalFormatSymbols(symbols.getULocale());
-
- if (symbols.getCurrencySymbol().equals(def.getCurrencySymbol())
- && symbols.getInternationalCurrencySymbol()
- .equals(def.getInternationalCurrencySymbol())) {
- setCurrency(Currency.getInstance(symbols.getULocale()));
- } else {
- setCurrency(null);
- }
- }
-
- /**
- * Returns the positive prefix.
- *
- * <p>Examples: +123, $123, sFr123
- * @return the prefix
- * @stable ICU 2.0
- */
- public String getPositivePrefix() {
- return positivePrefix;
- }
-
- /**
- * Sets the positive prefix.
- *
- * <p>Examples: +123, $123, sFr123
- * @param newValue the prefix
- * @stable ICU 2.0
- */
- public void setPositivePrefix(String newValue) {
- positivePrefix = newValue;
- posPrefixPattern = null;
- }
-
- /**
- * Returns the negative prefix.
- *
- * <p>Examples: -123, ($123) (with negative suffix), sFr-123
- *
- * @return the prefix
- * @stable ICU 2.0
- */
- public String getNegativePrefix() {
- return negativePrefix;
- }
-
- /**
- * Sets the negative prefix.
- *
- * <p>Examples: -123, ($123) (with negative suffix), sFr-123
- * @param newValue the prefix
- * @stable ICU 2.0
- */
- public void setNegativePrefix(String newValue) {
- negativePrefix = newValue;
- negPrefixPattern = null;
- }
-
- /**
- * Returns the positive suffix.
- *
- * <p>Example: 123%
- *
- * @return the suffix
- * @stable ICU 2.0
- */
- public String getPositiveSuffix() {
- return positiveSuffix;
- }
-
- /**
- * Sets the positive suffix.
- *
- * <p>Example: 123%
- * @param newValue the suffix
- * @stable ICU 2.0
- */
- public void setPositiveSuffix(String newValue) {
- positiveSuffix = newValue;
- posSuffixPattern = null;
- }
-
- /**
- * Returns the negative suffix.
- *
- * <p>Examples: -123%, ($123) (with positive suffixes)
- *
- * @return the suffix
- * @stable ICU 2.0
- */
- public String getNegativeSuffix() {
- return negativeSuffix;
- }
-
- /**
- * Sets the positive suffix.
- *
- * <p>Examples: 123%
- * @param newValue the suffix
- * @stable ICU 2.0
- */
- public void setNegativeSuffix(String newValue) {
- negativeSuffix = newValue;
- negSuffixPattern = null;
- }
-
- /**
- * Returns the multiplier for use in percent, permill, etc. For a percentage, set the
- * suffixes to have "%" and the multiplier to be 100. (For Arabic, use arabic percent
- * symbol). For a permill, set the suffixes to have "\u2031" and the multiplier to be
- * 1000.
- *
- * <p>Examples: with 100, 1.23 -> "123", and "123" -> 1.23
- *
- * @return the multiplier
- * @stable ICU 2.0
- */
- public int getMultiplier() {
- return multiplier;
- }
-
- /**
- * Sets the multiplier for use in percent, permill, etc. For a percentage, set the
- * suffixes to have "%" and the multiplier to be 100. (For Arabic, use arabic percent
- * symbol). For a permill, set the suffixes to have "\u2031" and the multiplier to be
- * 1000.
- *
- * <p>Examples: with 100, 1.23 -> "123", and "123" -> 1.23
- *
- * @param newValue the multiplier
- * @stable ICU 2.0
- */
- public void setMultiplier(int newValue) {
- if (newValue == 0) {
- throw new IllegalArgumentException("Bad multiplier: " + newValue);
- }
- multiplier = newValue;
- }
-
- /**
- * {@icu} Returns the rounding increment.
- *
- * @return A positive rounding increment, or <code>null</code> if a custom rounding
- * increment is not in effect.
- * @see #setRoundingIncrement
- * @see #getRoundingMode
- * @see #setRoundingMode
- * @stable ICU 2.0
- */
- public java.math.BigDecimal getRoundingIncrement() {
- if (roundingIncrementICU == null)
- return null;
- return roundingIncrementICU.toBigDecimal();
- }
-
- /**
- * {@icu} Sets the rounding increment. In the absence of a rounding increment, numbers
- * will be rounded to the number of digits displayed.
- *
- * @param newValue A positive rounding increment, or <code>null</code> or
- * <code>BigDecimal(0.0)</code> to use the default rounding increment.
- * @throws IllegalArgumentException if <code>newValue</code> is < 0.0
- * @see #getRoundingIncrement
- * @see #getRoundingMode
- * @see #setRoundingMode
- * @stable ICU 2.0
- */
- public void setRoundingIncrement(java.math.BigDecimal newValue) {
- if (newValue == null) {
- setRoundingIncrement((BigDecimal) null);
- } else {
- setRoundingIncrement(new BigDecimal(newValue));
- }
- }
-
- /**
- * {@icu} Sets the rounding increment. In the absence of a rounding increment, numbers
- * will be rounded to the number of digits displayed.
- *
- * @param newValue A positive rounding increment, or <code>null</code> or
- * <code>BigDecimal(0.0)</code> to use the default rounding increment.
- * @throws IllegalArgumentException if <code>newValue</code> is < 0.0
- * @see #getRoundingIncrement
- * @see #getRoundingMode
- * @see #setRoundingMode
- * @stable ICU 3.6
- */
- public void setRoundingIncrement(BigDecimal newValue) {
- int i = newValue == null ? 0 : newValue.compareTo(BigDecimal.ZERO);
- if (i < 0) {
- throw new IllegalArgumentException("Illegal rounding increment");
- }
- if (i == 0) {
- setInternalRoundingIncrement(null);
- } else {
- setInternalRoundingIncrement(newValue);
- }
- resetActualRounding();
- }
-
- /**
- * {@icu} Sets the rounding increment. In the absence of a rounding increment, numbers
- * will be rounded to the number of digits displayed.
- *
- * @param newValue A positive rounding increment, or 0.0 to use the default
- * rounding increment.
- * @throws IllegalArgumentException if <code>newValue</code> is < 0.0
- * @see #getRoundingIncrement
- * @see #getRoundingMode
- * @see #setRoundingMode
- * @stable ICU 2.0
- */
- public void setRoundingIncrement(double newValue) {
- if (newValue < 0.0) {
- throw new IllegalArgumentException("Illegal rounding increment");
- }
- if (newValue == 0.0d) {
- setInternalRoundingIncrement((BigDecimal) null);
- } else {
- // Should use BigDecimal#valueOf(double) instead of constructor
- // to avoid the double precision problem.
- setInternalRoundingIncrement(BigDecimal.valueOf(newValue));
- }
- resetActualRounding();
- }
-
- /**
- * Returns the rounding mode.
- *
- * @return A rounding mode, between <code>BigDecimal.ROUND_UP</code> and
- * <code>BigDecimal.ROUND_UNNECESSARY</code>.
- * @see #setRoundingIncrement
- * @see #getRoundingIncrement
- * @see #setRoundingMode
- * @see java.math.BigDecimal
- * @stable ICU 2.0
- */
- @Override
- public int getRoundingMode() {
- return roundingMode;
- }
-
- /**
- * Sets the rounding mode. This has no effect unless the rounding increment is greater
- * than zero.
- *
- * @param roundingMode A rounding mode, between <code>BigDecimal.ROUND_UP</code> and
- * <code>BigDecimal.ROUND_UNNECESSARY</code>.
- * @exception IllegalArgumentException if <code>roundingMode</code> is unrecognized.
- * @see #setRoundingIncrement
- * @see #getRoundingIncrement
- * @see #getRoundingMode
- * @see java.math.BigDecimal
- * @stable ICU 2.0
- */
- @Override
- public void setRoundingMode(int roundingMode) {
- if (roundingMode < BigDecimal.ROUND_UP || roundingMode > BigDecimal.ROUND_UNNECESSARY) {
- throw new IllegalArgumentException("Invalid rounding mode: " + roundingMode);
- }
-
- this.roundingMode = roundingMode;
- resetActualRounding();
- }
-
- /**
- * Returns the width to which the output of <code>format()</code> is padded. The width is
- * counted in 16-bit code units.
- *
- * @return the format width, or zero if no padding is in effect
- * @see #setFormatWidth
- * @see #getPadCharacter
- * @see #setPadCharacter
- * @see #getPadPosition
- * @see #setPadPosition
- * @stable ICU 2.0
- */
- public int getFormatWidth() {
- return formatWidth;
- }
-
- /**
- * Sets the width to which the output of <code>format()</code> is
- * padded. The width is counted in 16-bit code units. This method
- * also controls whether padding is enabled.
- *
- * @param width the width to which to pad the result of
- * <code>format()</code>, or zero to disable padding
- * @exception IllegalArgumentException if <code>width</code> is < 0
- * @see #getFormatWidth
- * @see #getPadCharacter
- * @see #setPadCharacter
- * @see #getPadPosition
- * @see #setPadPosition
- * @stable ICU 2.0
- */
- public void setFormatWidth(int width) {
- if (width < 0) {
- throw new IllegalArgumentException("Illegal format width");
- }
- formatWidth = width;
- }
-
- /**
- * {@icu} Returns the character used to pad to the format width. The default is ' '.
- *
- * @return the pad character
- * @see #setFormatWidth
- * @see #getFormatWidth
- * @see #setPadCharacter
- * @see #getPadPosition
- * @see #setPadPosition
- * @stable ICU 2.0
- */
- public char getPadCharacter() {
- return pad;
- }
-
- /**
- * {@icu} Sets the character used to pad to the format width. If padding is not
- * enabled, then this will take effect if padding is later enabled.
- *
- * @param padChar the pad character
- * @see #setFormatWidth
- * @see #getFormatWidth
- * @see #getPadCharacter
- * @see #getPadPosition
- * @see #setPadPosition
- * @stable ICU 2.0
- */
- public void setPadCharacter(char padChar) {
- pad = padChar;
- }
-
- /**
- * {@icu} Returns the position at which padding will take place. This is the location at
- * which padding will be inserted if the result of <code>format()</code> is shorter
- * than the format width.
- *
- * @return the pad position, one of <code>PAD_BEFORE_PREFIX</code>,
- * <code>PAD_AFTER_PREFIX</code>, <code>PAD_BEFORE_SUFFIX</code>, or
- * <code>PAD_AFTER_SUFFIX</code>.
- * @see #setFormatWidth
- * @see #getFormatWidth
- * @see #setPadCharacter
- * @see #getPadCharacter
- * @see #setPadPosition
- * @see #PAD_BEFORE_PREFIX
- * @see #PAD_AFTER_PREFIX
- * @see #PAD_BEFORE_SUFFIX
- * @see #PAD_AFTER_SUFFIX
- * @stable ICU 2.0
- */
- public int getPadPosition() {
- return padPosition;
- }
-
- /**
- * {@icu} Sets the position at which padding will take place. This is the location at
- * which padding will be inserted if the result of <code>format()</code> is shorter
- * than the format width. This has no effect unless padding is enabled.
- *
- * @param padPos the pad position, one of <code>PAD_BEFORE_PREFIX</code>,
- * <code>PAD_AFTER_PREFIX</code>, <code>PAD_BEFORE_SUFFIX</code>, or
- * <code>PAD_AFTER_SUFFIX</code>.
- * @exception IllegalArgumentException if the pad position in unrecognized
- * @see #setFormatWidth
- * @see #getFormatWidth
- * @see #setPadCharacter
- * @see #getPadCharacter
- * @see #getPadPosition
- * @see #PAD_BEFORE_PREFIX
- * @see #PAD_AFTER_PREFIX
- * @see #PAD_BEFORE_SUFFIX
- * @see #PAD_AFTER_SUFFIX
- * @stable ICU 2.0
- */
- public void setPadPosition(int padPos) {
- if (padPos < PAD_BEFORE_PREFIX || padPos > PAD_AFTER_SUFFIX) {
- throw new IllegalArgumentException("Illegal pad position");
- }
- padPosition = padPos;
- }
-
- /**
- * {@icu} Returns whether or not scientific notation is used.
- *
- * @return true if this object formats and parses scientific notation
- * @see #setScientificNotation
- * @see #getMinimumExponentDigits
- * @see #setMinimumExponentDigits
- * @see #isExponentSignAlwaysShown
- * @see #setExponentSignAlwaysShown
- * @stable ICU 2.0
- */
- public boolean isScientificNotation() {
- return useExponentialNotation;
- }
-
- /**
- * {@icu} Sets whether or not scientific notation is used. When scientific notation is
- * used, the effective maximum number of integer digits is <= 8. If the maximum number
- * of integer digits is set to more than 8, the effective maximum will be 1. This
- * allows this call to generate a 'default' scientific number format without
- * additional changes.
- *
- * @param useScientific true if this object formats and parses scientific notation
- * @see #isScientificNotation
- * @see #getMinimumExponentDigits
- * @see #setMinimumExponentDigits
- * @see #isExponentSignAlwaysShown
- * @see #setExponentSignAlwaysShown
- * @stable ICU 2.0
- */
- public void setScientificNotation(boolean useScientific) {
- useExponentialNotation = useScientific;
- }
-
- /**
- * {@icu} Returns the minimum exponent digits that will be shown.
- *
- * @return the minimum exponent digits that will be shown
- * @see #setScientificNotation
- * @see #isScientificNotation
- * @see #setMinimumExponentDigits
- * @see #isExponentSignAlwaysShown
- * @see #setExponentSignAlwaysShown
- * @stable ICU 2.0
- */
- public byte getMinimumExponentDigits() {
- return minExponentDigits;
- }
-
- /**
- * {@icu} Sets the minimum exponent digits that will be shown. This has no effect
- * unless scientific notation is in use.
- *
- * @param minExpDig a value >= 1 indicating the fewest exponent
- * digits that will be shown
- * @exception IllegalArgumentException if <code>minExpDig</code> < 1
- * @see #setScientificNotation
- * @see #isScientificNotation
- * @see #getMinimumExponentDigits
- * @see #isExponentSignAlwaysShown
- * @see #setExponentSignAlwaysShown
- * @stable ICU 2.0
- */
- public void setMinimumExponentDigits(byte minExpDig) {
- if (minExpDig < 1) {
- throw new IllegalArgumentException("Exponent digits must be >= 1");
- }
- minExponentDigits = minExpDig;
- }
-
- /**
- * {@icu} Returns whether the exponent sign is always shown.
- *
- * @return true if the exponent is always prefixed with either the localized minus
- * sign or the localized plus sign, false if only negative exponents are prefixed with
- * the localized minus sign.
- * @see #setScientificNotation
- * @see #isScientificNotation
- * @see #setMinimumExponentDigits
- * @see #getMinimumExponentDigits
- * @see #setExponentSignAlwaysShown
- * @stable ICU 2.0
- */
- public boolean isExponentSignAlwaysShown() {
- return exponentSignAlwaysShown;
- }
-
- /**
- * {@icu} Sets whether the exponent sign is always shown. This has no effect unless
- * scientific notation is in use.
- *
- * @param expSignAlways true if the exponent is always prefixed with either the
- * localized minus sign or the localized plus sign, false if only negative exponents
- * are prefixed with the localized minus sign.
- * @see #setScientificNotation
- * @see #isScientificNotation
- * @see #setMinimumExponentDigits
- * @see #getMinimumExponentDigits
- * @see #isExponentSignAlwaysShown
- * @stable ICU 2.0
- */
- public void setExponentSignAlwaysShown(boolean expSignAlways) {
- exponentSignAlwaysShown = expSignAlways;
- }
-
- /**
- * Returns the grouping size. Grouping size is the number of digits between grouping
- * separators in the integer portion of a number. For example, in the number
- * "123,456.78", the grouping size is 3.
- *
- * @see #setGroupingSize
- * @see NumberFormat#isGroupingUsed
- * @see DecimalFormatSymbols#getGroupingSeparator
- * @stable ICU 2.0
- */
- public int getGroupingSize() {
- return groupingSize;
- }
-
- /**
- * Sets the grouping size. Grouping size is the number of digits between grouping
- * separators in the integer portion of a number. For example, in the number
- * "123,456.78", the grouping size is 3.
- *
- * @see #getGroupingSize
- * @see NumberFormat#setGroupingUsed
- * @see DecimalFormatSymbols#setGroupingSeparator
- * @stable ICU 2.0
- */
- public void setGroupingSize(int newValue) {
- groupingSize = (byte) newValue;
- }
-
- /**
- * {@icu} Returns the secondary grouping size. In some locales one grouping interval
- * is used for the least significant integer digits (the primary grouping size), and
- * another is used for all others (the secondary grouping size). A formatter
- * supporting a secondary grouping size will return a positive integer unequal to the
- * primary grouping size returned by <code>getGroupingSize()</code>. For example, if
- * the primary grouping size is 4, and the secondary grouping size is 2, then the
- * number 123456789 formats as "1,23,45,6789", and the pattern appears as "#,##,###0".
- *
- * @return the secondary grouping size, or a value less than one if there is none
- * @see #setSecondaryGroupingSize
- * @see NumberFormat#isGroupingUsed
- * @see DecimalFormatSymbols#getGroupingSeparator
- * @stable ICU 2.0
- */
- public int getSecondaryGroupingSize() {
- return groupingSize2;
- }
-
- /**
- * {@icu} Sets the secondary grouping size. If set to a value less than 1, then
- * secondary grouping is turned off, and the primary grouping size is used for all
- * intervals, not just the least significant.
- *
- * @see #getSecondaryGroupingSize
- * @see NumberFormat#setGroupingUsed
- * @see DecimalFormatSymbols#setGroupingSeparator
- * @stable ICU 2.0
- */
- public void setSecondaryGroupingSize(int newValue) {
- groupingSize2 = (byte) newValue;
- }
-
- /**
- * {@icu} Returns the MathContext used by this format.
- *
- * @return desired MathContext
- * @see #getMathContext
- * @stable ICU 4.2
- */
- public MathContext getMathContextICU() {
- return mathContext;
- }
-
- /**
- * {@icu} Returns the MathContext used by this format.
- *
- * @return desired MathContext
- * @see #getMathContext
- * @stable ICU 4.2
- */
- public java.math.MathContext getMathContext() {
- try {
- // don't allow multiple references
- return mathContext == null ? null : new java.math.MathContext(mathContext.getDigits(),
- java.math.RoundingMode.valueOf(mathContext.getRoundingMode()));
- } catch (Exception foo) {
- return null; // should never happen
- }
- }
-
- /**
- * {@icu} Sets the MathContext used by this format.
- *
- * @param newValue desired MathContext
- * @see #getMathContext
- * @stable ICU 4.2
- */
- public void setMathContextICU(MathContext newValue) {
- mathContext = newValue;
- }
-
- /**
- * {@icu} Sets the MathContext used by this format.
- *
- * @param newValue desired MathContext
- * @see #getMathContext
- * @stable ICU 4.2
- */
- public void setMathContext(java.math.MathContext newValue) {
- mathContext = new MathContext(newValue.getPrecision(), MathContext.SCIENTIFIC, false,
- (newValue.getRoundingMode()).ordinal());
- }
-
- /**
- * Returns the behavior of the decimal separator with integers. (The decimal
- * separator will always appear with decimals.) <p> Example: Decimal ON: 12345 ->
- * 12345.; OFF: 12345 -> 12345
- *
- * @stable ICU 2.0
- */
- public boolean isDecimalSeparatorAlwaysShown() {
- return decimalSeparatorAlwaysShown;
- }
-
- /**
- * When decimal match is not required, the input does not have to
- * contain a decimal mark when there is a decimal mark specified in the
- * pattern.
- * @param value true if input must contain a match to decimal mark in pattern
- * Default is false.
- * @stable ICU 54
- */
- public void setDecimalPatternMatchRequired(boolean value) {
- parseRequireDecimalPoint = value;
- }
-
- /**
- * {@icu} Returns whether the input to parsing must contain a decimal mark if there
- * is a decimal mark in the pattern.
- * @return true if input must contain a match to decimal mark in pattern
- * @stable ICU 54
- */
- public boolean isDecimalPatternMatchRequired() {
- return parseRequireDecimalPoint;
- }
-
-
- /**
- * Sets the behavior of the decimal separator with integers. (The decimal separator
- * will always appear with decimals.)
- *
- * <p>This only affects formatting, and only where there might be no digits after the
- * decimal point, e.g., if true, 3456.00 -> "3,456." if false, 3456.00 -> "3456" This
- * is independent of parsing. If you want parsing to stop at the decimal point, use
- * setParseIntegerOnly.
- *
- * <p>
- * Example: Decimal ON: 12345 -> 12345.; OFF: 12345 -> 12345
- *
- * @stable ICU 2.0
- */
- public void setDecimalSeparatorAlwaysShown(boolean newValue) {
- decimalSeparatorAlwaysShown = newValue;
- }
-
- /**
- * {@icu} Returns a copy of the CurrencyPluralInfo used by this format. It might
- * return null if the decimal format is not a plural type currency decimal
- * format. Plural type currency decimal format means either the pattern in the decimal
- * format contains 3 currency signs, or the decimal format is initialized with
- * PLURALCURRENCYSTYLE.
- *
- * @return desired CurrencyPluralInfo
- * @see CurrencyPluralInfo
- * @stable ICU 4.2
- */
- public CurrencyPluralInfo getCurrencyPluralInfo() {
- try {
- // don't allow multiple references
- return currencyPluralInfo == null ? null :
- (CurrencyPluralInfo) currencyPluralInfo.clone();
- } catch (Exception foo) {
- return null; // should never happen
- }
- }
-
- /**
- * {@icu} Sets the CurrencyPluralInfo used by this format. The format uses a copy of
- * the provided information.
- *
- * @param newInfo desired CurrencyPluralInfo
- * @see CurrencyPluralInfo
- * @stable ICU 4.2
- */
- public void setCurrencyPluralInfo(CurrencyPluralInfo newInfo) {
- currencyPluralInfo = (CurrencyPluralInfo) newInfo.clone();
- isReadyForParsing = false;
- }
-
- /**
- * Overrides clone.
- * @stable ICU 2.0
- */
- @Override
- public Object clone() {
- try {
- DecimalFormat_ICU58 other = (DecimalFormat_ICU58) super.clone();
- other.symbols = (DecimalFormatSymbols) symbols.clone();
- other.digitList = new DigitList(); // fix for JB#5358
- if (currencyPluralInfo != null) {
- other.currencyPluralInfo = (CurrencyPluralInfo) currencyPluralInfo.clone();
- }
- other.attributes = new ArrayList<FieldPosition>(); // #9240
- other.currencyUsage = currencyUsage;
-
- // TODO: We need to figure out whether we share a single copy of DigitList by
- // multiple cloned copies. format/subformat are designed to use a single
- // instance, but parse/subparse implementation is not.
- return other;
- } catch (Exception e) {
- throw new IllegalStateException();
- }
- }
-
- /**
- * Overrides equals.
- * @stable ICU 2.0
- */
- @Override
- public boolean equals(Object obj) {
- if (obj == null)
- return false;
- if (!super.equals(obj))
- return false; // super does class check
-
- DecimalFormat_ICU58 other = (DecimalFormat_ICU58) obj;
- // Add the comparison of the four new added fields ,they are posPrefixPattern,
- // posSuffixPattern, negPrefixPattern, negSuffixPattern. [Richard/GCL]
- // following are added to accomodate changes for currency plural format.
- return currencySignCount == other.currencySignCount
- && (style != NumberFormat.PLURALCURRENCYSTYLE ||
- equals(posPrefixPattern, other.posPrefixPattern)
- && equals(posSuffixPattern, other.posSuffixPattern)
- && equals(negPrefixPattern, other.negPrefixPattern)
- && equals(negSuffixPattern, other.negSuffixPattern))
- && multiplier == other.multiplier
- && groupingSize == other.groupingSize
- && groupingSize2 == other.groupingSize2
- && decimalSeparatorAlwaysShown == other.decimalSeparatorAlwaysShown
- && useExponentialNotation == other.useExponentialNotation
- && (!useExponentialNotation || minExponentDigits == other.minExponentDigits)
- && useSignificantDigits == other.useSignificantDigits
- && (!useSignificantDigits || minSignificantDigits == other.minSignificantDigits
- && maxSignificantDigits == other.maxSignificantDigits)
- && symbols.equals(other.symbols)
- && Utility.objectEquals(currencyPluralInfo, other.currencyPluralInfo)
- && currencyUsage.equals(other.currencyUsage);
- }
-
- // method to unquote the strings and compare
- private boolean equals(String pat1, String pat2) {
- if (pat1 == null || pat2 == null) {
- return (pat1 == null && pat2 == null);
- }
- // fast path
- if (pat1.equals(pat2)) {
- return true;
- }
- return unquote(pat1).equals(unquote(pat2));
- }
-
- private String unquote(String pat) {
- StringBuilder buf = new StringBuilder(pat.length());
- int i = 0;
- while (i < pat.length()) {
- char ch = pat.charAt(i++);
- if (ch != QUOTE) {
- buf.append(ch);
- }
- }
- return buf.toString();
- }
-
- // protected void handleToString(StringBuffer buf) {
- // buf.append("\nposPrefixPattern: '" + posPrefixPattern + "'\n");
- // buf.append("positivePrefix: '" + positivePrefix + "'\n");
- // buf.append("posSuffixPattern: '" + posSuffixPattern + "'\n");
- // buf.append("positiveSuffix: '" + positiveSuffix + "'\n");
- // buf.append("negPrefixPattern: '" +
- // com.ibm.icu.impl.Utility.format1ForSource(negPrefixPattern) + "'\n");
- // buf.append("negativePrefix: '" +
- // com.ibm.icu.impl.Utility.format1ForSource(negativePrefix) + "'\n");
- // buf.append("negSuffixPattern: '" + negSuffixPattern + "'\n");
- // buf.append("negativeSuffix: '" + negativeSuffix + "'\n");
- // buf.append("multiplier: '" + multiplier + "'\n");
- // buf.append("groupingSize: '" + groupingSize + "'\n");
- // buf.append("groupingSize2: '" + groupingSize2 + "'\n");
- // buf.append("decimalSeparatorAlwaysShown: '" + decimalSeparatorAlwaysShown + "'\n");
- // buf.append("useExponentialNotation: '" + useExponentialNotation + "'\n");
- // buf.append("minExponentDigits: '" + minExponentDigits + "'\n");
- // buf.append("useSignificantDigits: '" + useSignificantDigits + "'\n");
- // buf.append("minSignificantDigits: '" + minSignificantDigits + "'\n");
- // buf.append("maxSignificantDigits: '" + maxSignificantDigits + "'\n");
- // buf.append("symbols: '" + symbols + "'");
- // }
-
- /**
- * Overrides hashCode.
- * @stable ICU 2.0
- */
- @Override
- public int hashCode() {
- return super.hashCode() * 37 + positivePrefix.hashCode();
- // just enough fields for a reasonable distribution
- }
-
- /**
- * Synthesizes a pattern string that represents the current state of this Format
- * object.
- *
- * @see #applyPattern
- * @stable ICU 2.0
- */
- public String toPattern() {
- if (style == NumberFormat.PLURALCURRENCYSTYLE) {
- // the prefix or suffix pattern might not be defined yet, so they can not be
- // synthesized, instead, get them directly. but it might not be the actual
- // pattern used in formatting. the actual pattern used in formatting depends
- // on the formatted number's plural count.
- return formatPattern;
- }
- return toPattern(false);
- }
-
- /**
- * Synthesizes a localized pattern string that represents the current state of this
- * Format object.
- *
- * @see #applyPattern
- * @stable ICU 2.0
- */
- public String toLocalizedPattern() {
- if (style == NumberFormat.PLURALCURRENCYSTYLE) {
- return formatPattern;
- }
- return toPattern(true);
- }
-
- /**
- * Expands the affix pattern strings into the expanded affix strings. If any affix
- * pattern string is null, do not expand it. This method should be called any time the
- * symbols or the affix patterns change in order to keep the expanded affix strings up
- * to date. This method also will be called before formatting if format currency
- * plural names, since the plural name is not a static one, it is based on the
- * currency plural count, the affix will be known only after the currency plural count
- * is know. In which case, the parameter 'pluralCount' will be a non-null currency
- * plural count. In all other cases, the 'pluralCount' is null, which means it is not
- * needed.
- */
- // Bug 4212072 [Richard/GCL]
- private void expandAffixes(String pluralCount) {
- // expandAffix() will set currencyChoice to a non-null value if
- // appropriate AND if it is null.
- currencyChoice = null;
-
- // Reuse one StringBuffer for better performance
- StringBuffer buffer = new StringBuffer();
- if (posPrefixPattern != null) {
- expandAffix(posPrefixPattern, pluralCount, buffer);
- positivePrefix = buffer.toString();
- }
- if (posSuffixPattern != null) {
- expandAffix(posSuffixPattern, pluralCount, buffer);
- positiveSuffix = buffer.toString();
- }
- if (negPrefixPattern != null) {
- expandAffix(negPrefixPattern, pluralCount, buffer);
- negativePrefix = buffer.toString();
- }
- if (negSuffixPattern != null) {
- expandAffix(negSuffixPattern, pluralCount, buffer);
- negativeSuffix = buffer.toString();
- }
- }
-
- /**
- * Expands an affix pattern into an affix string. All characters in the pattern are
- * literal unless bracketed by QUOTEs. The following characters outside QUOTE are
- * recognized: PATTERN_PERCENT, PATTERN_PER_MILLE, PATTERN_MINUS, and
- * CURRENCY_SIGN. If CURRENCY_SIGN is doubled, it is interpreted as an international
- * currency sign. If CURRENCY_SIGN is tripled, it is interpreted as currency plural
- * long names, such as "US Dollars". Any other character outside QUOTE represents
- * itself. Quoted text must be well-formed.
- *
- * This method is used in two distinct ways. First, it is used to expand the stored
- * affix patterns into actual affixes. For this usage, doFormat must be false. Second,
- * it is used to expand the stored affix patterns given a specific number (doFormat ==
- * true), for those rare cases in which a currency format references a ChoiceFormat
- * (e.g., en_IN display name for INR). The number itself is taken from digitList.
- * TODO: There are no currency ChoiceFormat patterns, figure out what is still relevant here.
- *
- * When used in the first way, this method has a side effect: It sets currencyChoice
- * to a ChoiceFormat object, if the currency's display name in this locale is a
- * ChoiceFormat pattern (very rare). It only does this if currencyChoice is null to
- * start with.
- *
- * @param pattern the non-null, possibly empty pattern
- * @param pluralCount the plural count. It is only used for currency plural format. In
- * which case, it is the plural count of the currency amount. For example, in en_US,
- * it is the singular "one", or the plural "other". For all other cases, it is null,
- * and is not being used.
- * @param buffer a scratch StringBuffer; its contents will be lost
- */
- // Bug 4212072 [Richard/GCL]
- private void expandAffix(String pattern, String pluralCount, StringBuffer buffer) {
- buffer.setLength(0);
- for (int i = 0; i < pattern.length();) {
- char c = pattern.charAt(i++);
- if (c == QUOTE) {
- for (;;) {
- int j = pattern.indexOf(QUOTE, i);
- if (j == i) {
- buffer.append(QUOTE);
- i = j + 1;
- break;
- } else if (j > i) {
- buffer.append(pattern.substring(i, j));
- i = j + 1;
- if (i < pattern.length() && pattern.charAt(i) == QUOTE) {
- buffer.append(QUOTE);
- ++i;
- // loop again
- } else {
- break;
- }
- } else {
- // Unterminated quote; should be caught by apply
- // pattern.
- throw new RuntimeException();
- }
- }
- continue;
- }
-
- switch (c) {
- case CURRENCY_SIGN:
- // As of ICU 2.2 we use the currency object, and ignore the currency
- // symbols in the DFS, unless we have a null currency object. This occurs
- // if resurrecting a pre-2.2 object or if the user sets a custom DFS.
- boolean intl = i < pattern.length() && pattern.charAt(i) == CURRENCY_SIGN;
- boolean plural = false;
- if (intl) {
- ++i;
- if (i < pattern.length() && pattern.charAt(i) == CURRENCY_SIGN) {
- plural = true;
- intl = false;
- ++i;
- }
- }
- String s = null;
- Currency currency = getCurrency();
- if (currency != null) {
- // plural name is only needed when pluralCount != null, which means
- // when formatting currency plural names. For other cases,
- // pluralCount == null, and plural names are not needed.
- if (plural && pluralCount != null) {
- s = currency.getName(symbols.getULocale(), Currency.PLURAL_LONG_NAME,
- pluralCount, null);
- } else if (!intl) {
- s = currency.getName(symbols.getULocale(), Currency.SYMBOL_NAME, null);
- } else {
- s = currency.getCurrencyCode();
- }
- } else {
- s = intl ? symbols.getInternationalCurrencySymbol() :
- symbols.getCurrencySymbol();
- }
- // Here is where FieldPosition could be set for CURRENCY PLURAL.
- buffer.append(s);
- break;
- case PATTERN_PERCENT:
- buffer.append(symbols.getPercentString());
- break;
- case PATTERN_PER_MILLE:
- buffer.append(symbols.getPerMillString());
- break;
- case PATTERN_MINUS_SIGN:
- buffer.append(symbols.getMinusSignString());
- break;
- default:
- buffer.append(c);
- break;
- }
- }
- }
-
- /**
- * Append an affix to the given StringBuffer.
- *
- * @param buf
- * buffer to append to
- * @param isNegative
- * @param isPrefix
- * @param fieldPosition
- * @param parseAttr
- */
- private int appendAffix(StringBuffer buf, boolean isNegative, boolean isPrefix,
- FieldPosition fieldPosition,
- boolean parseAttr) {
- if (currencyChoice != null) {
- String affixPat = null;
- if (isPrefix) {
- affixPat = isNegative ? negPrefixPattern : posPrefixPattern;
- } else {
- affixPat = isNegative ? negSuffixPattern : posSuffixPattern;
- }
- StringBuffer affixBuf = new StringBuffer();
- expandAffix(affixPat, null, affixBuf);
- buf.append(affixBuf);
- return affixBuf.length();
- }
-
- String affix = null;
- String pattern;
- if (isPrefix) {
- affix = isNegative ? negativePrefix : positivePrefix;
- pattern = isNegative ? negPrefixPattern : posPrefixPattern;
- } else {
- affix = isNegative ? negativeSuffix : positiveSuffix;
- pattern = isNegative ? negSuffixPattern : posSuffixPattern;
- }
- // [Spark/CDL] Invoke formatAffix2Attribute to add attributes for affix
- if (parseAttr) {
- // Updates for Ticket 11805.
- int offset = affix.indexOf(symbols.getCurrencySymbol());
- if (offset > -1) {
- formatAffix2Attribute(isPrefix, Field.CURRENCY, buf, offset,
- symbols.getCurrencySymbol().length());
- }
- offset = affix.indexOf(symbols.getMinusSignString());
- if (offset > -1) {
- formatAffix2Attribute(isPrefix, Field.SIGN, buf, offset,
- symbols.getMinusSignString().length());
- }
- offset = affix.indexOf(symbols.getPercentString());
- if (offset > -1) {
- formatAffix2Attribute(isPrefix, Field.PERCENT, buf, offset,
- symbols.getPercentString().length());
- }
- offset = affix.indexOf(symbols.getPerMillString());
- if (offset > -1) {
- formatAffix2Attribute(isPrefix, Field.PERMILLE, buf, offset,
- symbols.getPerMillString().length());
- }
- offset = pattern.indexOf("¤¤¤");
- if (offset > -1) {
- formatAffix2Attribute(isPrefix, Field.CURRENCY, buf, offset,
- affix.length() - offset);
- }
- }
-
- // Look for SIGN, PERCENT, PERMILLE in the formatted affix.
- if (fieldPosition.getFieldAttribute() == NumberFormat.Field.SIGN) {
- String sign = isNegative ? symbols.getMinusSignString() : symbols.getPlusSignString();
- int firstPos = affix.indexOf(sign);
- if (firstPos > -1) {
- int startPos = buf.length() + firstPos;
- fieldPosition.setBeginIndex(startPos);
- fieldPosition.setEndIndex(startPos + sign.length());
- }
- } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.PERCENT) {
- int firstPos = affix.indexOf(symbols.getPercentString());
- if (firstPos > -1) {
- int startPos = buf.length() + firstPos;
- fieldPosition.setBeginIndex(startPos);
- fieldPosition.setEndIndex(startPos + symbols.getPercentString().length());
- }
- } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.PERMILLE) {
- int firstPos = affix.indexOf(symbols.getPerMillString());
- if (firstPos > -1) {
- int startPos = buf.length() + firstPos;
- fieldPosition.setBeginIndex(startPos);
- fieldPosition.setEndIndex(startPos + symbols.getPerMillString().length());
- }
- } else
- // If CurrencySymbol or InternationalCurrencySymbol is in the affix, check for currency symbol.
- // Get spelled out name if "¤¤¤" is in the pattern.
- if (fieldPosition.getFieldAttribute() == NumberFormat.Field.CURRENCY) {
- if (affix.indexOf(symbols.getCurrencySymbol()) > -1) {
- String aff = symbols.getCurrencySymbol();
- int firstPos = affix.indexOf(aff);
- int start = buf.length() + firstPos;
- int end = start + aff.length();
- fieldPosition.setBeginIndex(start);
- fieldPosition.setEndIndex(end);
- } else if (affix.indexOf(symbols.getInternationalCurrencySymbol()) > -1) {
- String aff = symbols.getInternationalCurrencySymbol();
- int firstPos = affix.indexOf(aff);
- int start = buf.length() + firstPos;
- int end = start + aff.length();
- fieldPosition.setBeginIndex(start);
- fieldPosition.setEndIndex(end);
- } else if (pattern.indexOf("¤¤¤") > -1) {
- // It's a plural, and we know where it is in the pattern.
- int firstPos = pattern.indexOf("¤¤¤");
- int start = buf.length() + firstPos;
- int end = buf.length() + affix.length(); // This seems clunky and wrong.
- fieldPosition.setBeginIndex(start);
- fieldPosition.setEndIndex(end);
- }
- }
-
- buf.append(affix);
- return affix.length();
- }
-
- // Fix for prefix and suffix in Ticket 11805.
- private void formatAffix2Attribute(boolean isPrefix, Field fieldType,
- StringBuffer buf, int offset, int symbolSize) {
- int begin;
- begin = offset;
- if (!isPrefix) {
- begin += buf.length();
- }
-
- addAttribute(fieldType, begin, begin + symbolSize);
- }
-
- /**
- * [Spark/CDL] Use this method to add attribute.
- */
- private void addAttribute(Field field, int begin, int end) {
- FieldPosition pos = new FieldPosition(field);
- pos.setBeginIndex(begin);
- pos.setEndIndex(end);
- attributes.add(pos);
- }
-
- /**
- * Formats the object to an attributed string, and return the corresponding iterator.
- *
- * @stable ICU 3.6
- */
- @Override
- public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
- return formatToCharacterIterator(obj, NULL_UNIT);
- }
-
- AttributedCharacterIterator formatToCharacterIterator(Object obj, Unit unit) {
- if (!(obj instanceof Number))
- throw new IllegalArgumentException();
- Number number = (Number) obj;
- StringBuffer text = new StringBuffer();
- unit.writePrefix(text);
- attributes.clear();
- if (obj instanceof BigInteger) {
- format((BigInteger) number, text, new FieldPosition(0), true);
- } else if (obj instanceof java.math.BigDecimal) {
- format((java.math.BigDecimal) number, text, new FieldPosition(0)
- , true);
- } else if (obj instanceof Double) {
- format(number.doubleValue(), text, new FieldPosition(0), true);
- } else if (obj instanceof Integer || obj instanceof Long) {
- format(number.longValue(), text, new FieldPosition(0), true);
- } else {
- throw new IllegalArgumentException();
- }
- unit.writeSuffix(text);
- AttributedString as = new AttributedString(text.toString());
-
- // add NumberFormat field attributes to the AttributedString
- for (int i = 0; i < attributes.size(); i++) {
- FieldPosition pos = attributes.get(i);
- Format.Field attribute = pos.getFieldAttribute();
- as.addAttribute(attribute, attribute, pos.getBeginIndex(), pos.getEndIndex());
- }
-
- // return the CharacterIterator from AttributedString
- return as.getIterator();
- }
-
- /**
- * Appends an affix pattern to the given StringBuffer. Localize unquoted specials.
- * <p>
- * <b>Note:</b> This implementation does not support new String localized symbols.
- */
- private void appendAffixPattern(StringBuffer buffer, boolean isNegative, boolean isPrefix,
- boolean localized) {
- String affixPat = null;
- if (isPrefix) {
- affixPat = isNegative ? negPrefixPattern : posPrefixPattern;
- } else {
- affixPat = isNegative ? negSuffixPattern : posSuffixPattern;
- }
-
- // When there is a null affix pattern, we use the affix itself.
- if (affixPat == null) {
- String affix = null;
- if (isPrefix) {
- affix = isNegative ? negativePrefix : positivePrefix;
- } else {
- affix = isNegative ? negativeSuffix : positiveSuffix;
- }
- // Do this crudely for now: Wrap everything in quotes.
- buffer.append(QUOTE);
- for (int i = 0; i < affix.length(); ++i) {
- char ch = affix.charAt(i);
- if (ch == QUOTE) {
- buffer.append(ch);
- }
- buffer.append(ch);
- }
- buffer.append(QUOTE);
- return;
- }
-
- if (!localized) {
- buffer.append(affixPat);
- } else {
- int i, j;
- for (i = 0; i < affixPat.length(); ++i) {
- char ch = affixPat.charAt(i);
- switch (ch) {
- case QUOTE:
- j = affixPat.indexOf(QUOTE, i + 1);
- if (j < 0) {
- throw new IllegalArgumentException("Malformed affix pattern: " + affixPat);
- }
- buffer.append(affixPat.substring(i, j + 1));
- i = j;
- continue;
- case PATTERN_PER_MILLE:
- ch = symbols.getPerMill();
- break;
- case PATTERN_PERCENT:
- ch = symbols.getPercent();
- break;
- case PATTERN_MINUS_SIGN:
- ch = symbols.getMinusSign();
- break;
- }
- // check if char is same as any other symbol
- if (ch == symbols.getDecimalSeparator() || ch == symbols.getGroupingSeparator()) {
- buffer.append(QUOTE);
- buffer.append(ch);
- buffer.append(QUOTE);
- } else {
- buffer.append(ch);
- }
- }
- }
- }
-
- /**
- * Does the real work of generating a pattern.
- * <p>
- * <b>Note:</b> This implementation does not support new String localized symbols.
- */
- private String toPattern(boolean localized) {
- StringBuffer result = new StringBuffer();
- char zero = localized ? symbols.getZeroDigit() : PATTERN_ZERO_DIGIT;
- char digit = localized ? symbols.getDigit() : PATTERN_DIGIT;
- char sigDigit = 0;
- boolean useSigDig = areSignificantDigitsUsed();
- if (useSigDig) {
- sigDigit = localized ? symbols.getSignificantDigit() : PATTERN_SIGNIFICANT_DIGIT;
- }
- char group = localized ? symbols.getGroupingSeparator() : PATTERN_GROUPING_SEPARATOR;
- int i;
- int roundingDecimalPos = 0; // Pos of decimal in roundingDigits
- String roundingDigits = null;
- int padPos = (formatWidth > 0) ? padPosition : -1;
- String padSpec = (formatWidth > 0)
- ? new StringBuffer(2).append(localized
- ? symbols.getPadEscape()
- : PATTERN_PAD_ESCAPE).append(pad).toString()
- : null;
- if (roundingIncrementICU != null) {
- i = roundingIncrementICU.scale();
- roundingDigits = roundingIncrementICU.movePointRight(i).toString();
- roundingDecimalPos = roundingDigits.length() - i;
- }
- for (int part = 0; part < 2; ++part) {
- // variable not used int partStart = result.length();
- if (padPos == PAD_BEFORE_PREFIX) {
- result.append(padSpec);
- }
-
- // Use original symbols read from resources in pattern eg. use "\u00A4"
- // instead of "$" in Locale.US [Richard/GCL]
- appendAffixPattern(result, part != 0, true, localized);
- if (padPos == PAD_AFTER_PREFIX) {
- result.append(padSpec);
- }
- int sub0Start = result.length();
- int g = isGroupingUsed() ? Math.max(0, groupingSize) : 0;
- if (g > 0 && groupingSize2 > 0 && groupingSize2 != groupingSize) {
- g += groupingSize2;
- }
- int maxDig = 0, minDig = 0, maxSigDig = 0;
- if (useSigDig) {
- minDig = getMinimumSignificantDigits();
- maxDig = maxSigDig = getMaximumSignificantDigits();
- } else {
- minDig = getMinimumIntegerDigits();
- maxDig = getMaximumIntegerDigits();
- }
- if (useExponentialNotation) {
- if (maxDig > MAX_SCIENTIFIC_INTEGER_DIGITS) {
- maxDig = 1;
- }
- } else if (useSigDig) {
- maxDig = Math.max(maxDig, g + 1);
- } else {
- maxDig = Math.max(Math.max(g, getMinimumIntegerDigits()), roundingDecimalPos) + 1;
- }
- for (i = maxDig; i > 0; --i) {
- if (!useExponentialNotation && i < maxDig && isGroupingPosition(i)) {
- result.append(group);
- }
- if (useSigDig) {
- // #@,@### (maxSigDig == 5, minSigDig == 2) 65 4321 (1-based pos,
- // count from the right) Use # if pos > maxSigDig or 1 <= pos <=
- // (maxSigDig - minSigDig) Use @ if (maxSigDig - minSigDig) < pos <=
- // maxSigDig
- result.append((maxSigDig >= i && i > (maxSigDig - minDig)) ? sigDigit : digit);
- } else {
- if (roundingDigits != null) {
- int pos = roundingDecimalPos - i;
- if (pos >= 0 && pos < roundingDigits.length()) {
- result.append((char) (roundingDigits.charAt(pos) - '0' + zero));
- continue;
- }
- }
- result.append(i <= minDig ? zero : digit);
- }
- }
- if (!useSigDig) {
- if (getMaximumFractionDigits() > 0 || decimalSeparatorAlwaysShown) {
- result.append(localized ? symbols.getDecimalSeparator() :
- PATTERN_DECIMAL_SEPARATOR);
- }
- int pos = roundingDecimalPos;
- for (i = 0; i < getMaximumFractionDigits(); ++i) {
- if (roundingDigits != null && pos < roundingDigits.length()) {
- result.append(pos < 0 ? zero :
- (char) (roundingDigits.charAt(pos) - '0' + zero));
- ++pos;
- continue;
- }
- result.append(i < getMinimumFractionDigits() ? zero : digit);
- }
- }
- if (useExponentialNotation) {
- if (localized) {
- result.append(symbols.getExponentSeparator());
- } else {
- result.append(PATTERN_EXPONENT);
- }
- if (exponentSignAlwaysShown) {
- result.append(localized ? symbols.getPlusSign() : PATTERN_PLUS_SIGN);
- }
- for (i = 0; i < minExponentDigits; ++i) {
- result.append(zero);
- }
- }
- if (padSpec != null && !useExponentialNotation) {
- int add = formatWidth
- - result.length()
- + sub0Start
- - ((part == 0)
- ? positivePrefix.length() + positiveSuffix.length()
- : negativePrefix.length() + negativeSuffix.length());
- while (add > 0) {
- result.insert(sub0Start, digit);
- ++maxDig;
- --add;
- // Only add a grouping separator if we have at least 2 additional
- // characters to be added, so we don't end up with ",###".
- if (add > 1 && isGroupingPosition(maxDig)) {
- result.insert(sub0Start, group);
- --add;
- }
- }
- }
- if (padPos == PAD_BEFORE_SUFFIX) {
- result.append(padSpec);
- }
- // Use original symbols read from resources in pattern eg. use "\u00A4"
- // instead of "$" in Locale.US [Richard/GCL]
- appendAffixPattern(result, part != 0, false, localized);
- if (padPos == PAD_AFTER_SUFFIX) {
- result.append(padSpec);
- }
- if (part == 0) {
- if (negativeSuffix.equals(positiveSuffix) &&
- negativePrefix.equals(PATTERN_MINUS_SIGN + positivePrefix)) {
- break;
- } else {
- result.append(localized ? symbols.getPatternSeparator() : PATTERN_SEPARATOR);
- }
- }
- }
- return result.toString();
- }
-
- /**
- * Applies the given pattern to this Format object. A pattern is a short-hand
- * specification for the various formatting properties. These properties can also be
- * changed individually through the various setter methods.
- *
- * <p>There is no limit to integer digits are set by this routine, since that is the
- * typical end-user desire; use setMaximumInteger if you want to set a real value. For
- * negative numbers, use a second pattern, separated by a semicolon
- *
- * <p>Example "#,#00.0#" -> 1,234.56
- *
- * <p>This means a minimum of 2 integer digits, 1 fraction digit, and a maximum of 2
- * fraction digits.
- *
- * <p>Example: "#,#00.0#;(#,#00.0#)" for negatives in parentheses.
- *
- * <p>In negative patterns, the minimum and maximum counts are ignored; these are
- * presumed to be set in the positive pattern.
- *
- * @stable ICU 2.0
- */
- public void applyPattern(String pattern) {
- applyPattern(pattern, false);
- }
-
- /**
- * Applies the given pattern to this Format object. The pattern is assumed to be in a
- * localized notation. A pattern is a short-hand specification for the various
- * formatting properties. These properties can also be changed individually through
- * the various setter methods.
- *
- * <p>There is no limit to integer digits are set by this routine, since that is the
- * typical end-user desire; use setMaximumInteger if you want to set a real value. For
- * negative numbers, use a second pattern, separated by a semicolon
- *
- * <p>Example "#,#00.0#" -> 1,234.56
- *
- * <p>This means a minimum of 2 integer digits, 1 fraction digit, and a maximum of 2
- * fraction digits.
- *
- * <p>Example: "#,#00.0#;(#,#00.0#)" for negatives in parantheses.
- *
- * <p>In negative patterns, the minimum and maximum counts are ignored; these are
- * presumed to be set in the positive pattern.
- *
- * @stable ICU 2.0
- */
- public void applyLocalizedPattern(String pattern) {
- applyPattern(pattern, true);
- }
-
- /**
- * Does the real work of applying a pattern.
- */
- private void applyPattern(String pattern, boolean localized) {
- applyPatternWithoutExpandAffix(pattern, localized);
- expandAffixAdjustWidth(null);
- }
-
- private void expandAffixAdjustWidth(String pluralCount) {
- // Bug 4212072 Update the affix strings according to symbols in order to keep the
- // affix strings up to date. [Richard/GCL]
- expandAffixes(pluralCount);
-
- // Now that we have the actual prefix and suffix, fix up formatWidth
- if (formatWidth > 0) {
- formatWidth += positivePrefix.length() + positiveSuffix.length();
- }
- }
-
- private void applyPatternWithoutExpandAffix(String pattern, boolean localized) {
- char zeroDigit = PATTERN_ZERO_DIGIT; // '0'
- char sigDigit = PATTERN_SIGNIFICANT_DIGIT; // '@'
- char groupingSeparator = PATTERN_GROUPING_SEPARATOR;
- char decimalSeparator = PATTERN_DECIMAL_SEPARATOR;
- char percent = PATTERN_PERCENT;
- char perMill = PATTERN_PER_MILLE;
- char digit = PATTERN_DIGIT; // '#'
- char separator = PATTERN_SEPARATOR;
- String exponent = String.valueOf(PATTERN_EXPONENT);
- char plus = PATTERN_PLUS_SIGN;
- char padEscape = PATTERN_PAD_ESCAPE;
- char minus = PATTERN_MINUS_SIGN; // Bug 4212072 [Richard/GCL]
- if (localized) {
- zeroDigit = symbols.getZeroDigit();
- sigDigit = symbols.getSignificantDigit();
- groupingSeparator = symbols.getGroupingSeparator();
- decimalSeparator = symbols.getDecimalSeparator();
- percent = symbols.getPercent();
- perMill = symbols.getPerMill();
- digit = symbols.getDigit();
- separator = symbols.getPatternSeparator();
- exponent = symbols.getExponentSeparator();
- plus = symbols.getPlusSign();
- padEscape = symbols.getPadEscape();
- minus = symbols.getMinusSign(); // Bug 4212072 [Richard/GCL]
- }
- char nineDigit = (char) (zeroDigit + 9);
-
- boolean gotNegative = false;
-
- int pos = 0;
- // Part 0 is the positive pattern. Part 1, if present, is the negative
- // pattern.
- for (int part = 0; part < 2 && pos < pattern.length(); ++part) {
- // The subpart ranges from 0 to 4: 0=pattern proper, 1=prefix, 2=suffix,
- // 3=prefix in quote, 4=suffix in quote. Subpart 0 is between the prefix and
- // suffix, and consists of pattern characters. In the prefix and suffix,
- // percent, permille, and currency symbols are recognized and translated.
- int subpart = 1, sub0Start = 0, sub0Limit = 0, sub2Limit = 0;
-
- // It's important that we don't change any fields of this object
- // prematurely. We set the following variables for the multiplier, grouping,
- // etc., and then only change the actual object fields if everything parses
- // correctly. This also lets us register the data from part 0 and ignore the
- // part 1, except for the prefix and suffix.
- StringBuilder prefix = new StringBuilder();
- StringBuilder suffix = new StringBuilder();
- int decimalPos = -1;
- int multpl = 1;
- int digitLeftCount = 0, zeroDigitCount = 0, digitRightCount = 0, sigDigitCount = 0;
- byte groupingCount = -1;
- byte groupingCount2 = -1;
- int padPos = -1;
- char padChar = 0;
- int incrementPos = -1;
- long incrementVal = 0;
- byte expDigits = -1;
- boolean expSignAlways = false;
- int currencySignCnt = 0;
-
- // The affix is either the prefix or the suffix.
- StringBuilder affix = prefix;
-
- int start = pos;
-
- PARTLOOP: for (; pos < pattern.length(); ++pos) {
- char ch = pattern.charAt(pos);
- switch (subpart) {
- case 0: // Pattern proper subpart (between prefix & suffix)
- // Process the digits, decimal, and grouping characters. We record
- // five pieces of information. We expect the digits to occur in the
- // pattern ####00.00####, and we record the number of left digits,
- // zero (central) digits, and right digits. The position of the last
- // grouping character is recorded (should be somewhere within the
- // first two blocks of characters), as is the position of the decimal
- // point, if any (should be in the zero digits). If there is no
- // decimal point, then there should be no right digits.
- if (ch == digit) {
- if (zeroDigitCount > 0 || sigDigitCount > 0) {
- ++digitRightCount;
- } else {
- ++digitLeftCount;
- }
- if (groupingCount >= 0 && decimalPos < 0) {
- ++groupingCount;
- }
- } else if ((ch >= zeroDigit && ch <= nineDigit) || ch == sigDigit) {
- if (digitRightCount > 0) {
- patternError("Unexpected '" + ch + '\'', pattern);
- }
- if (ch == sigDigit) {
- ++sigDigitCount;
- } else {
- ++zeroDigitCount;
- if (ch != zeroDigit) {
- int p = digitLeftCount + zeroDigitCount + digitRightCount;
- if (incrementPos >= 0) {
- while (incrementPos < p) {
- incrementVal *= 10;
- ++incrementPos;
- }
- } else {
- incrementPos = p;
- }
- incrementVal += ch - zeroDigit;
- }
- }
- if (groupingCount >= 0 && decimalPos < 0) {
- ++groupingCount;
- }
- } else if (ch == groupingSeparator) {
- // Bug 4212072 process the Localized pattern like
- // "'Fr. '#'##0.05;'Fr.-'#'##0.05" (Locale="CH", groupingSeparator
- // == QUOTE) [Richard/GCL]
- if (ch == QUOTE && (pos + 1) < pattern.length()) {
- char after = pattern.charAt(pos + 1);
- if (!(after == digit || (after >= zeroDigit && after <= nineDigit))) {
- // A quote outside quotes indicates either the opening
- // quote or two quotes, which is a quote literal. That is,
- // we have the first quote in 'do' or o''clock.
- if (after == QUOTE) {
- ++pos;
- // Fall through to append(ch)
- } else {
- if (groupingCount < 0) {
- subpart = 3; // quoted prefix subpart
- } else {
- // Transition to suffix subpart
- subpart = 2; // suffix subpart
- affix = suffix;
- sub0Limit = pos--;
- }
- continue;
- }
- }
- }
-
- if (decimalPos >= 0) {
- patternError("Grouping separator after decimal", pattern);
- }
- groupingCount2 = groupingCount;
- groupingCount = 0;
- } else if (ch == decimalSeparator) {
- if (decimalPos >= 0) {
- patternError("Multiple decimal separators", pattern);
- }
- // Intentionally incorporate the digitRightCount, even though it
- // is illegal for this to be > 0 at this point. We check pattern
- // syntax below.
- decimalPos = digitLeftCount + zeroDigitCount + digitRightCount;
- } else {
- if (pattern.regionMatches(pos, exponent, 0, exponent.length())) {
- if (expDigits >= 0) {
- patternError("Multiple exponential symbols", pattern);
- }
- if (groupingCount >= 0) {
- patternError("Grouping separator in exponential", pattern);
- }
- pos += exponent.length();
- // Check for positive prefix
- if (pos < pattern.length() && pattern.charAt(pos) == plus) {
- expSignAlways = true;
- ++pos;
- }
- // Use lookahead to parse out the exponential part of the
- // pattern, then jump into suffix subpart.
- expDigits = 0;
- while (pos < pattern.length() && pattern.charAt(pos) == zeroDigit) {
- ++expDigits;
- ++pos;
- }
-
- // 1. Require at least one mantissa pattern digit
- // 2. Disallow "#+ @" in mantissa
- // 3. Require at least one exponent pattern digit
- if (((digitLeftCount + zeroDigitCount) < 1 &&
- (sigDigitCount + digitRightCount) < 1)
- || (sigDigitCount > 0 && digitLeftCount > 0) || expDigits < 1) {
- patternError("Malformed exponential", pattern);
- }
- }
- // Transition to suffix subpart
- subpart = 2; // suffix subpart
- affix = suffix;
- sub0Limit = pos--; // backup: for() will increment
- continue;
- }
- break;
- case 1: // Prefix subpart
- case 2: // Suffix subpart
- // Process the prefix / suffix characters Process unquoted characters
- // seen in prefix or suffix subpart.
-
- // Several syntax characters implicitly begins the next subpart if we
- // are in the prefix; otherwise they are illegal if unquoted.
- if (ch == digit || ch == groupingSeparator || ch == decimalSeparator
- || (ch >= zeroDigit && ch <= nineDigit) || ch == sigDigit) {
- // Any of these characters implicitly begins the
- // next subpart if we are in the prefix
- if (subpart == 1) { // prefix subpart
- subpart = 0; // pattern proper subpart
- sub0Start = pos--; // Reprocess this character
- continue;
- } else if (ch == QUOTE) {
- // Bug 4212072 process the Localized pattern like
- // "'Fr. '#'##0.05;'Fr.-'#'##0.05" (Locale="CH",
- // groupingSeparator == QUOTE) [Richard/GCL]
-
- // A quote outside quotes indicates either the opening quote
- // or two quotes, which is a quote literal. That is, we have
- // the first quote in 'do' or o''clock.
- if ((pos + 1) < pattern.length() && pattern.charAt(pos + 1) == QUOTE) {
- ++pos;
- affix.append(ch);
- } else {
- subpart += 2; // open quote
- }
- continue;
- }
- patternError("Unquoted special character '" + ch + '\'', pattern);
- } else if (ch == CURRENCY_SIGN) {
- // Use lookahead to determine if the currency sign is
- // doubled or not.
- boolean doubled = (pos + 1) < pattern.length() &&
- pattern.charAt(pos + 1) == CURRENCY_SIGN;
-
- // Bug 4212072 To meet the need of expandAffix(String,
- // StirngBuffer) [Richard/GCL]
- if (doubled) {
- ++pos; // Skip over the doubled character
- affix.append(ch); // append two: one here, one below
- if ((pos + 1) < pattern.length() &&
- pattern.charAt(pos + 1) == CURRENCY_SIGN) {
- ++pos; // Skip over the tripled character
- affix.append(ch); // append again
- currencySignCnt = CURRENCY_SIGN_COUNT_IN_PLURAL_FORMAT;
- } else {
- currencySignCnt = CURRENCY_SIGN_COUNT_IN_ISO_FORMAT;
- }
- } else {
- currencySignCnt = CURRENCY_SIGN_COUNT_IN_SYMBOL_FORMAT;
- }
- // Fall through to append(ch)
- } else if (ch == QUOTE) {
- // A quote outside quotes indicates either the opening quote or
- // two quotes, which is a quote literal. That is, we have the
- // first quote in 'do' or o''clock.
- if ((pos + 1) < pattern.length() && pattern.charAt(pos + 1) == QUOTE) {
- ++pos;
- affix.append(ch); // append two: one here, one below
- } else {
- subpart += 2; // open quote
- }
- // Fall through to append(ch)
- } else if (ch == separator) {
- // Don't allow separators in the prefix, and don't allow
- // separators in the second pattern (part == 1).
- if (subpart == 1 || part == 1) {
- patternError("Unquoted special character '" + ch + '\'', pattern);
- }
- sub2Limit = pos++;
- break PARTLOOP; // Go to next part
- } else if (ch == percent || ch == perMill) {
- // Next handle characters which are appended directly.
- if (multpl != 1) {
- patternError("Too many percent/permille characters", pattern);
- }
- multpl = (ch == percent) ? 100 : 1000;
- // Convert to non-localized pattern
- ch = (ch == percent) ? PATTERN_PERCENT : PATTERN_PER_MILLE;
- // Fall through to append(ch)
- } else if (ch == minus) {
- // Convert to non-localized pattern
- ch = PATTERN_MINUS_SIGN;
- // Fall through to append(ch)
- } else if (ch == padEscape) {
- if (padPos >= 0) {
- patternError("Multiple pad specifiers", pattern);
- }
- if ((pos + 1) == pattern.length()) {
- patternError("Invalid pad specifier", pattern);
- }
- padPos = pos++; // Advance past pad char
- padChar = pattern.charAt(pos);
- continue;
- }
- affix.append(ch);
- break;
- case 3: // Prefix subpart, in quote
- case 4: // Suffix subpart, in quote
- // A quote within quotes indicates either the closing quote or two
- // quotes, which is a quote literal. That is, we have the second quote
- // in 'do' or 'don''t'.
- if (ch == QUOTE) {
- if ((pos + 1) < pattern.length() && pattern.charAt(pos + 1) == QUOTE) {
- ++pos;
- affix.append(ch);
- } else {
- subpart -= 2; // close quote
- }
- // Fall through to append(ch)
- }
- // NOTE: In ICU 2.2 there was code here to parse quoted percent and
- // permille characters _within quotes_ and give them special
- // meaning. This is incorrect, since quoted characters are literals
- // without special meaning.
- affix.append(ch);
- break;
- }
- }
-
- if (subpart == 3 || subpart == 4) {
- patternError("Unterminated quote", pattern);
- }
-
- if (sub0Limit == 0) {
- sub0Limit = pattern.length();
- }
-
- if (sub2Limit == 0) {
- sub2Limit = pattern.length();
- }
-
- // Handle patterns with no '0' pattern character. These patterns are legal,
- // but must be recodified to make sense. "##.###" -> "#0.###". ".###" ->
- // ".0##".
- //
- // We allow patterns of the form "####" to produce a zeroDigitCount of zero
- // (got that?); although this seems like it might make it possible for
- // format() to produce empty strings, format() checks for this condition and
- // outputs a zero digit in this situation. Having a zeroDigitCount of zero
- // yields a minimum integer digits of zero, which allows proper round-trip
- // patterns. We don't want "#" to become "#0" when toPattern() is called (even
- // though that's what it really is, semantically).
- if (zeroDigitCount == 0 && sigDigitCount == 0 &&
- digitLeftCount > 0 && decimalPos >= 0) {
- // Handle "###.###" and "###." and ".###"
- int n = decimalPos;
- if (n == 0)
- ++n; // Handle ".###"
- digitRightCount = digitLeftCount - n;
- digitLeftCount = n - 1;
- zeroDigitCount = 1;
- }
-
- // Do syntax checking on the digits, decimal points, and quotes.
- if ((decimalPos < 0 && digitRightCount > 0 && sigDigitCount == 0)
- || (decimalPos >= 0
- && (sigDigitCount > 0
- || decimalPos < digitLeftCount
- || decimalPos > (digitLeftCount + zeroDigitCount)))
- || groupingCount == 0
- || groupingCount2 == 0
- || (sigDigitCount > 0 && zeroDigitCount > 0)
- || subpart > 2) { // subpart > 2 == unmatched quote
- patternError("Malformed pattern", pattern);
- }
-
- // Make sure pad is at legal position before or after affix.
- if (padPos >= 0) {
- if (padPos == start) {
- padPos = PAD_BEFORE_PREFIX;
- } else if (padPos + 2 == sub0Start) {
- padPos = PAD_AFTER_PREFIX;
- } else if (padPos == sub0Limit) {
- padPos = PAD_BEFORE_SUFFIX;
- } else if (padPos + 2 == sub2Limit) {
- padPos = PAD_AFTER_SUFFIX;
- } else {
- patternError("Illegal pad position", pattern);
- }
- }
-
- if (part == 0) {
- // Set negative affixes temporarily to match the positive
- // affixes. Fix this up later after processing both parts.
-
- // Bug 4212072 To meet the need of expandAffix(String, StirngBuffer)
- // [Richard/GCL]
- posPrefixPattern = negPrefixPattern = prefix.toString();
- posSuffixPattern = negSuffixPattern = suffix.toString();
-
- useExponentialNotation = (expDigits >= 0);
- if (useExponentialNotation) {
- minExponentDigits = expDigits;
- exponentSignAlwaysShown = expSignAlways;
- }
- int digitTotalCount = digitLeftCount + zeroDigitCount + digitRightCount;
- // The effectiveDecimalPos is the position the decimal is at or would be
- // at if there is no decimal. Note that if decimalPos<0, then
- // digitTotalCount == digitLeftCount + zeroDigitCount.
- int effectiveDecimalPos = decimalPos >= 0 ? decimalPos : digitTotalCount;
- boolean useSigDig = (sigDigitCount > 0);
- setSignificantDigitsUsed(useSigDig);
- if (useSigDig) {
- setMinimumSignificantDigits(sigDigitCount);
- setMaximumSignificantDigits(sigDigitCount + digitRightCount);
- } else {
- int minInt = effectiveDecimalPos - digitLeftCount;
- setMinimumIntegerDigits(minInt);
-
- // Upper limit on integer and fraction digits for a Java double
- // [Richard/GCL]
- setMaximumIntegerDigits(useExponentialNotation ? digitLeftCount + minInt :
- DOUBLE_INTEGER_DIGITS);
- _setMaximumFractionDigits(decimalPos >= 0 ?
- (digitTotalCount - decimalPos) : 0);
- setMinimumFractionDigits(decimalPos >= 0 ?
- (digitLeftCount + zeroDigitCount - decimalPos) : 0);
- }
- setGroupingUsed(groupingCount > 0);
- this.groupingSize = (groupingCount > 0) ? groupingCount : 0;
- this.groupingSize2 = (groupingCount2 > 0 && groupingCount2 != groupingCount)
- ? groupingCount2 : 0;
- this.multiplier = multpl;
- setDecimalSeparatorAlwaysShown(decimalPos == 0 || decimalPos == digitTotalCount);
- if (padPos >= 0) {
- padPosition = padPos;
- formatWidth = sub0Limit - sub0Start; // to be fixed up below
- pad = padChar;
- } else {
- formatWidth = 0;
- }
- if (incrementVal != 0) {
- // BigDecimal scale cannot be negative (even though this makes perfect
- // sense), so we need to handle this.
- int scale = incrementPos - effectiveDecimalPos;
- roundingIncrementICU = BigDecimal.valueOf(incrementVal, scale > 0 ? scale : 0);
- if (scale < 0) {
- roundingIncrementICU = roundingIncrementICU.movePointRight(-scale);
- }
- roundingMode = BigDecimal.ROUND_HALF_EVEN;
- } else {
- setRoundingIncrement((BigDecimal) null);
- }
-
- // Update currency sign count for the new pattern
- currencySignCount = currencySignCnt;
- } else {
- // Bug 4212072 To meet the need of expandAffix(String, StirngBuffer)
- // [Richard/GCL]
- negPrefixPattern = prefix.toString();
- negSuffixPattern = suffix.toString();
- gotNegative = true;
- }
- }
-
-
- // Bug 4140009 Process the empty pattern [Richard/GCL]
- if (pattern.length() == 0) {
- posPrefixPattern = posSuffixPattern = "";
- setMinimumIntegerDigits(0);
- setMaximumIntegerDigits(DOUBLE_INTEGER_DIGITS);
- setMinimumFractionDigits(0);
- _setMaximumFractionDigits(DOUBLE_FRACTION_DIGITS);
- }
-
- // If there was no negative pattern, or if the negative pattern is identical to
- // the positive pattern, then prepend the minus sign to the positive pattern to
- // form the negative pattern.
-
- // Bug 4212072 To meet the need of expandAffix(String, StirngBuffer) [Richard/GCL]
-
- if (!gotNegative ||
- (negPrefixPattern.equals(posPrefixPattern)
- && negSuffixPattern.equals(posSuffixPattern))) {
- negSuffixPattern = posSuffixPattern;
- negPrefixPattern = PATTERN_MINUS_SIGN + posPrefixPattern;
- }
- setLocale(null, null);
- // save the pattern
- formatPattern = pattern;
-
- // special handlings for currency instance
- if (currencySignCount != CURRENCY_SIGN_COUNT_ZERO) {
- // reset rounding increment and max/min fractional digits
- // by the currency
- Currency theCurrency = getCurrency();
- if (theCurrency != null) {
- setRoundingIncrement(theCurrency.getRoundingIncrement(currencyUsage));
- int d = theCurrency.getDefaultFractionDigits(currencyUsage);
- setMinimumFractionDigits(d);
- _setMaximumFractionDigits(d);
- }
-
- // initialize currencyPluralInfo if needed
- if (currencySignCount == CURRENCY_SIGN_COUNT_IN_PLURAL_FORMAT
- && currencyPluralInfo == null) {
- currencyPluralInfo = new CurrencyPluralInfo(symbols.getULocale());
- }
- }
- resetActualRounding();
- }
-
-
- private void patternError(String msg, String pattern) {
- throw new IllegalArgumentException(msg + " in pattern \"" + pattern + '"');
- }
-
-
- // Rewrite the following 4 "set" methods Upper limit on integer and fraction digits
- // for a Java double [Richard/GCL]
-
- /**
- * Sets the maximum number of digits allowed in the integer portion of a number. This
- * override limits the integer digit count to 309.
- *
- * @see NumberFormat#setMaximumIntegerDigits
- * @stable ICU 2.0
- */
- @Override
- public void setMaximumIntegerDigits(int newValue) {
- super.setMaximumIntegerDigits(Math.min(newValue, DOUBLE_INTEGER_DIGITS));
- }
-
- /**
- * Sets the minimum number of digits allowed in the integer portion of a number. This
- * override limits the integer digit count to 309.
- *
- * @see NumberFormat#setMinimumIntegerDigits
- * @stable ICU 2.0
- */
- @Override
- public void setMinimumIntegerDigits(int newValue) {
- super.setMinimumIntegerDigits(Math.min(newValue, DOUBLE_INTEGER_DIGITS));
- }
-
- /**
- * {@icu} Returns the minimum number of significant digits that will be
- * displayed. This value has no effect unless {@link #areSignificantDigitsUsed()}
- * returns true.
- *
- * @return the fewest significant digits that will be shown
- * @stable ICU 3.0
- */
- public int getMinimumSignificantDigits() {
- return minSignificantDigits;
- }
-
- /**
- * {@icu} Returns the maximum number of significant digits that will be
- * displayed. This value has no effect unless {@link #areSignificantDigitsUsed()}
- * returns true.
- *
- * @return the most significant digits that will be shown
- * @stable ICU 3.0
- */
- public int getMaximumSignificantDigits() {
- return maxSignificantDigits;
- }
-
- /**
- * {@icu} Sets the minimum number of significant digits that will be displayed. If
- * <code>min</code> is less than one then it is set to one. If the maximum significant
- * digits count is less than <code>min</code>, then it is set to <code>min</code>.
- * This function also enables the use of significant digits by this formatter -
- * {@link #areSignificantDigitsUsed()} will return true.
- *
- * @param min the fewest significant digits to be shown
- * @stable ICU 3.0
- */
- public void setMinimumSignificantDigits(int min) {
- if (min < 1) {
- min = 1;
- }
- // pin max sig dig to >= min
- int max = Math.max(maxSignificantDigits, min);
- minSignificantDigits = min;
- maxSignificantDigits = max;
- setSignificantDigitsUsed(true);
- }
-
- /**
- * {@icu} Sets the maximum number of significant digits that will be displayed. If
- * <code>max</code> is less than one then it is set to one. If the minimum significant
- * digits count is greater than <code>max</code>, then it is set to <code>max</code>.
- * This function also enables the use of significant digits by this formatter -
- * {@link #areSignificantDigitsUsed()} will return true.
- *
- * @param max the most significant digits to be shown
- * @stable ICU 3.0
- */
- public void setMaximumSignificantDigits(int max) {
- if (max < 1) {
- max = 1;
- }
- // pin min sig dig to 1..max
- int min = Math.min(minSignificantDigits, max);
- minSignificantDigits = min;
- maxSignificantDigits = max;
- setSignificantDigitsUsed(true);
- }
-
- /**
- * {@icu} Returns true if significant digits are in use or false if integer and
- * fraction digit counts are in use.
- *
- * @return true if significant digits are in use
- * @stable ICU 3.0
- */
- public boolean areSignificantDigitsUsed() {
- return useSignificantDigits;
- }
-
- /**
- * {@icu} Sets whether significant digits are in use, or integer and fraction digit
- * counts are in use.
- *
- * @param useSignificantDigits true to use significant digits, or false to use integer
- * and fraction digit counts
- * @stable ICU 3.0
- */
- public void setSignificantDigitsUsed(boolean useSignificantDigits) {
- this.useSignificantDigits = useSignificantDigits;
- }
-
- /**
- * Sets the <tt>Currency</tt> object used to display currency amounts. This takes
- * effect immediately, if this format is a currency format. If this format is not a
- * currency format, then the currency object is used if and when this object becomes a
- * currency format through the application of a new pattern.
- *
- * @param theCurrency new currency object to use. Must not be null.
- * @stable ICU 2.2
- */
- @Override
- public void setCurrency(Currency theCurrency) {
- // If we are a currency format, then modify our affixes to
- // encode the currency symbol for the given currency in our
- // locale, and adjust the decimal digits and rounding for the
- // given currency.
-
- super.setCurrency(theCurrency);
- if (theCurrency != null) {
- String s = theCurrency.getName(symbols.getULocale(), Currency.SYMBOL_NAME, null);
- symbols.setCurrency(theCurrency);
- symbols.setCurrencySymbol(s);
- }
-
- if (currencySignCount != CURRENCY_SIGN_COUNT_ZERO) {
- if (theCurrency != null) {
- setRoundingIncrement(theCurrency.getRoundingIncrement(currencyUsage));
- int d = theCurrency.getDefaultFractionDigits(currencyUsage);
- setMinimumFractionDigits(d);
- setMaximumFractionDigits(d);
- }
- if (currencySignCount != CURRENCY_SIGN_COUNT_IN_PLURAL_FORMAT) {
- // This is not necessary for plural format type
- // because affixes will be resolved in subformat
- expandAffixes(null);
- }
- }
- }
-
- /**
- * Sets the <tt>Currency Usage</tt> object used to display currency.
- * This takes effect immediately, if this format is a
- * currency format.
- * @param newUsage new currency context object to use.
- * @stable ICU 54
- */
- public void setCurrencyUsage(CurrencyUsage newUsage) {
- if (newUsage == null) {
- throw new NullPointerException("return value is null at method AAA");
- }
- currencyUsage = newUsage;
- Currency theCurrency = this.getCurrency();
-
- // We set rounding/digit based on currency context
- if (theCurrency != null) {
- setRoundingIncrement(theCurrency.getRoundingIncrement(currencyUsage));
- int d = theCurrency.getDefaultFractionDigits(currencyUsage);
- setMinimumFractionDigits(d);
- _setMaximumFractionDigits(d);
- }
- }
-
- /**
- * Returns the <tt>Currency Usage</tt> object used to display currency
- * @stable ICU 54
- */
- public CurrencyUsage getCurrencyUsage() {
- return currencyUsage;
- }
-
- /**
- * Returns the currency in effect for this formatter. Subclasses should override this
- * method as needed. Unlike getCurrency(), this method should never return null.
- *
- * @internal
- * @deprecated This API is ICU internal only.
- */
- @Deprecated
- @Override
- protected Currency getEffectiveCurrency() {
- Currency c = getCurrency();
- if (c == null) {
- c = Currency.getInstance(symbols.getInternationalCurrencySymbol());
- }
- return c;
- }
-
- /**
- * Sets the maximum number of digits allowed in the fraction portion of a number. This
- * override limits the fraction digit count to 340.
- *
- * @see NumberFormat#setMaximumFractionDigits
- * @stable ICU 2.0
- */
- @Override
- public void setMaximumFractionDigits(int newValue) {
- _setMaximumFractionDigits(newValue);
- resetActualRounding();
- }
-
- /*
- * Internal method for DecimalFormat, setting maximum fractional digits
- * without triggering actual rounding recalculated.
- */
- private void _setMaximumFractionDigits(int newValue) {
- super.setMaximumFractionDigits(Math.min(newValue, DOUBLE_FRACTION_DIGITS));
- }
-
- /**
- * Sets the minimum number of digits allowed in the fraction portion of a number. This
- * override limits the fraction digit count to 340.
- *
- * @see NumberFormat#setMinimumFractionDigits
- * @stable ICU 2.0
- */
- @Override
- public void setMinimumFractionDigits(int newValue) {
- super.setMinimumFractionDigits(Math.min(newValue, DOUBLE_FRACTION_DIGITS));
- }
-
- /**
- * Sets whether {@link #parse(String, ParsePosition)} returns BigDecimal. The
- * default value is false.
- *
- * @param value true if {@link #parse(String, ParsePosition)}
- * returns BigDecimal.
- * @stable ICU 3.6
- */
- public void setParseBigDecimal(boolean value) {
- parseBigDecimal = value;
- }
-
- /**
- * Returns whether {@link #parse(String, ParsePosition)} returns BigDecimal.
- *
- * @return true if {@link #parse(String, ParsePosition)} returns BigDecimal.
- * @stable ICU 3.6
- */
- public boolean isParseBigDecimal() {
- return parseBigDecimal;
- }
-
- /**
- * Set the maximum number of exponent digits when parsing a number.
- * If the limit is set too high, an OutOfMemoryException may be triggered.
- * The default value is 1000.
- * @param newValue the new limit
- * @stable ICU 51
- */
- public void setParseMaxDigits(int newValue) {
- if (newValue > 0) {
- PARSE_MAX_EXPONENT = newValue;
- }
- }
-
- /**
- * Get the current maximum number of exponent digits when parsing a
- * number.
- * @return the maximum number of exponent digits for parsing
- * @stable ICU 51
- */
- public int getParseMaxDigits() {
- return PARSE_MAX_EXPONENT;
- }
-
- private void writeObject(ObjectOutputStream stream) throws IOException {
- // Ticket#6449 Format.Field instances are not serializable. When
- // formatToCharacterIterator is called, attributes (ArrayList) stores
- // FieldPosition instances with NumberFormat.Field. Because NumberFormat.Field is
- // not serializable, we need to clear the contents of the list when writeObject is
- // called. We could remove the field or make it transient, but it will break
- // serialization compatibility.
- attributes.clear();
-
- stream.defaultWriteObject();
- }
-
- /**
- * First, read the default serializable fields from the stream. Then if
- * <code>serialVersionOnStream</code> is less than 1, indicating that the stream was
- * written by JDK 1.1, initialize <code>useExponentialNotation</code> to false, since
- * it was not present in JDK 1.1. Finally, set serialVersionOnStream back to the
- * maximum allowed value so that default serialization will work properly if this
- * object is streamed out again.
- */
- private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
- stream.defaultReadObject();
-
- // Bug 4185761 validate fields [Richard/GCL]
-
- // We only need to check the maximum counts because NumberFormat .readObject has
- // already ensured that the maximum is greater than the minimum count.
-
- // Commented for compatibility with previous version, and reserved for further use
- // if (getMaximumIntegerDigits() > DOUBLE_INTEGER_DIGITS ||
- // getMaximumFractionDigits() > DOUBLE_FRACTION_DIGITS) { throw new
- // InvalidObjectException("Digit count out of range"); }
-
-
- // Truncate the maximumIntegerDigits to DOUBLE_INTEGER_DIGITS and
- // maximumFractionDigits to DOUBLE_FRACTION_DIGITS
-
- if (getMaximumIntegerDigits() > DOUBLE_INTEGER_DIGITS) {
- setMaximumIntegerDigits(DOUBLE_INTEGER_DIGITS);
- }
- if (getMaximumFractionDigits() > DOUBLE_FRACTION_DIGITS) {
- _setMaximumFractionDigits(DOUBLE_FRACTION_DIGITS);
- }
- if (serialVersionOnStream < 2) {
- exponentSignAlwaysShown = false;
- setInternalRoundingIncrement(null);
- roundingMode = BigDecimal.ROUND_HALF_EVEN;
- formatWidth = 0;
- pad = ' ';
- padPosition = PAD_BEFORE_PREFIX;
- if (serialVersionOnStream < 1) {
- // Didn't have exponential fields
- useExponentialNotation = false;
- }
- }
- if (serialVersionOnStream < 3) {
- // Versions prior to 3 do not store a currency object. Create one to match
- // the DecimalFormatSymbols object.
- setCurrencyForSymbols();
- }
- if (serialVersionOnStream < 4) {
- currencyUsage = CurrencyUsage.STANDARD;
- }
- serialVersionOnStream = currentSerialVersion;
- digitList = new DigitList();
-
- if (roundingIncrement != null) {
- setInternalRoundingIncrement(new BigDecimal(roundingIncrement));
- }
- resetActualRounding();
- }
-
- private void setInternalRoundingIncrement(BigDecimal value) {
- roundingIncrementICU = value;
- roundingIncrement = value == null ? null : value.toBigDecimal();
- }
-
- // ----------------------------------------------------------------------
- // INSTANCE VARIABLES
- // ----------------------------------------------------------------------
-
- private transient DigitList digitList = new DigitList();
-
- /**
- * The symbol used as a prefix when formatting positive numbers, e.g. "+".
- *
- * @serial
- * @see #getPositivePrefix
- */
- private String positivePrefix = "";
-
- /**
- * The symbol used as a suffix when formatting positive numbers. This is often an
- * empty string.
- *
- * @serial
- * @see #getPositiveSuffix
- */
- private String positiveSuffix = "";
-
- /**
- * The symbol used as a prefix when formatting negative numbers, e.g. "-".
- *
- * @serial
- * @see #getNegativePrefix
- */
- private String negativePrefix = "-";
-
- /**
- * The symbol used as a suffix when formatting negative numbers. This is often an
- * empty string.
- *
- * @serial
- * @see #getNegativeSuffix
- */
- private String negativeSuffix = "";
-
- /**
- * The prefix pattern for non-negative numbers. This variable corresponds to
- * <code>positivePrefix</code>.
- *
- * <p>This pattern is expanded by the method <code>expandAffix()</code> to
- * <code>positivePrefix</code> to update the latter to reflect changes in
- * <code>symbols</code>. If this variable is <code>null</code> then
- * <code>positivePrefix</code> is taken as a literal value that does not change when
- * <code>symbols</code> changes. This variable is always <code>null</code> for
- * <code>DecimalFormat</code> objects older than stream version 2 restored from
- * stream.
- *
- * @serial
- */
- // [Richard/GCL]
- private String posPrefixPattern;
-
- /**
- * The suffix pattern for non-negative numbers. This variable corresponds to
- * <code>positiveSuffix</code>. This variable is analogous to
- * <code>posPrefixPattern</code>; see that variable for further documentation.
- *
- * @serial
- */
- // [Richard/GCL]
- private String posSuffixPattern;
-
- /**
- * The prefix pattern for negative numbers. This variable corresponds to
- * <code>negativePrefix</code>. This variable is analogous to
- * <code>posPrefixPattern</code>; see that variable for further documentation.
- *
- * @serial
- */
- // [Richard/GCL]
- private String negPrefixPattern;
-
- /**
- * The suffix pattern for negative numbers. This variable corresponds to
- * <code>negativeSuffix</code>. This variable is analogous to
- * <code>posPrefixPattern</code>; see that variable for further documentation.
- *
- * @serial
- */
- // [Richard/GCL]
- private String negSuffixPattern;
-
- /**
- * Formatter for ChoiceFormat-based currency names. If this field is not null, then
- * delegate to it to format currency symbols.
- * TODO: This is obsolete: Remove, and design extensible serialization. ICU ticket #12090.
- *
- * @since ICU 2.6
- */
- private ChoiceFormat currencyChoice;
-
- /**
- * The multiplier for use in percent, permill, etc.
- *
- * @serial
- * @see #getMultiplier
- */
- private int multiplier = 1;
-
- /**
- * The number of digits between grouping separators in the integer portion of a
- * number. Must be greater than 0 if <code>NumberFormat.groupingUsed</code> is true.
- *
- * @serial
- * @see #getGroupingSize
- * @see NumberFormat#isGroupingUsed
- */
- private byte groupingSize = 3; // invariant, > 0 if useThousands
-
- /**
- * The secondary grouping size. This is only used for Hindi numerals, which use a
- * primary grouping of 3 and a secondary grouping of 2, e.g., "12,34,567". If this
- * value is less than 1, then secondary grouping is equal to the primary grouping.
- *
- */
- private byte groupingSize2 = 0;
-
- /**
- * If true, forces the decimal separator to always appear in a formatted number, even
- * if the fractional part of the number is zero.
- *
- * @serial
- * @see #isDecimalSeparatorAlwaysShown
- */
- private boolean decimalSeparatorAlwaysShown = false;
-
- /**
- * The <code>DecimalFormatSymbols</code> object used by this format. It contains the
- * symbols used to format numbers, e.g. the grouping separator, decimal separator, and
- * so on.
- *
- * @serial
- * @see #setDecimalFormatSymbols
- * @see DecimalFormatSymbols
- */
- private DecimalFormatSymbols symbols = null; // LIU new DecimalFormatSymbols();
-
- /**
- * True to use significant digits rather than integer and fraction digit counts.
- *
- * @serial
- * @since ICU 3.0
- */
- private boolean useSignificantDigits = false;
-
- /**
- * The minimum number of significant digits to show. Must be >= 1 and <=
- * maxSignificantDigits. Ignored unless useSignificantDigits == true.
- *
- * @serial
- * @since ICU 3.0
- */
- private int minSignificantDigits = 1;
-
- /**
- * The maximum number of significant digits to show. Must be >=
- * minSignficantDigits. Ignored unless useSignificantDigits == true.
- *
- * @serial
- * @since ICU 3.0
- */
- private int maxSignificantDigits = 6;
-
- /**
- * True to force the use of exponential (i.e. scientific) notation
- * when formatting numbers.
- *
- *<p> Note that the JDK 1.2 public API provides no way to set this
- * field, even though it is supported by the implementation and
- * the stream format. The intent is that this will be added to the
- * API in the future.
- *
- * @serial
- */
- private boolean useExponentialNotation; // Newly persistent in JDK 1.2
-
- /**
- * The minimum number of digits used to display the exponent when a number is
- * formatted in exponential notation. This field is ignored if
- * <code>useExponentialNotation</code> is not true.
- *
- * <p>Note that the JDK 1.2 public API provides no way to set this field, even though
- * it is supported by the implementation and the stream format. The intent is that
- * this will be added to the API in the future.
- *
- * @serial
- */
- private byte minExponentDigits; // Newly persistent in JDK 1.2
-
- /**
- * If true, the exponent is always prefixed with either the plus sign or the minus
- * sign. Otherwise, only negative exponents are prefixed with the minus sign. This has
- * no effect unless <code>useExponentialNotation</code> is true.
- *
- * @serial
- * @since AlphaWorks NumberFormat
- */
- private boolean exponentSignAlwaysShown = false;
-
- /**
- * The value to which numbers are rounded during formatting. For example, if the
- * rounding increment is 0.05, then 13.371 would be formatted as 13.350, assuming 3
- * fraction digits. Has the value <code>null</code> if rounding is not in effect, or a
- * positive value if rounding is in effect. Default value <code>null</code>.
- *
- * @serial
- * @since AlphaWorks NumberFormat
- */
- // Note: this is kept in sync with roundingIncrementICU.
- // it is only kept around to avoid a conversion when formatting a java.math.BigDecimal
- private java.math.BigDecimal roundingIncrement = null;
-
- /**
- * The value to which numbers are rounded during formatting. For example, if the
- * rounding increment is 0.05, then 13.371 would be formatted as 13.350, assuming 3
- * fraction digits. Has the value <code>null</code> if rounding is not in effect, or a
- * positive value if rounding is in effect. Default value <code>null</code>. WARNING:
- * the roundingIncrement value is the one serialized.
- *
- * @serial
- * @since AlphaWorks NumberFormat
- */
- private transient BigDecimal roundingIncrementICU = null;
-
- /**
- * The rounding mode. This value controls any rounding operations which occur when
- * applying a rounding increment or when reducing the number of fraction digits to
- * satisfy a maximum fraction digits limit. The value may assume any of the
- * <code>BigDecimal</code> rounding mode values. Default value
- * <code>BigDecimal.ROUND_HALF_EVEN</code>.
- *
- * @serial
- * @since AlphaWorks NumberFormat
- */
- private int roundingMode = BigDecimal.ROUND_HALF_EVEN;
-
- /**
- * Operations on <code>BigDecimal</code> numbers are controlled by a {@link
- * MathContext} object, which provides the context (precision and other information)
- * for the operation. The default <code>MathContext</code> settings are
- * <code>digits=0, form=PLAIN, lostDigits=false, roundingMode=ROUND_HALF_UP</code>;
- * these settings perform fixed point arithmetic with unlimited precision, as defined
- * for the original BigDecimal class in Java 1.1 and Java 1.2
- */
- // context for plain unlimited math
- private MathContext mathContext = new MathContext(0, MathContext.PLAIN);
-
- /**
- * The padded format width, or zero if there is no padding. Must be >= 0. Default
- * value zero.
- *
- * @serial
- * @since AlphaWorks NumberFormat
- */
- private int formatWidth = 0;
-
- /**
- * The character used to pad the result of format to <code>formatWidth</code>, if
- * padding is in effect. Default value ' '.
- *
- * @serial
- * @since AlphaWorks NumberFormat
- */
- private char pad = ' ';
-
- /**
- * The position in the string at which the <code>pad</code> character will be
- * inserted, if padding is in effect. Must have a value from
- * <code>PAD_BEFORE_PREFIX</code> to <code>PAD_AFTER_SUFFIX</code>. Default value
- * <code>PAD_BEFORE_PREFIX</code>.
- *
- * @serial
- * @since AlphaWorks NumberFormat
- */
- private int padPosition = PAD_BEFORE_PREFIX;
-
- /**
- * True if {@link #parse(String, ParsePosition)} to return BigDecimal rather than
- * Long, Double or BigDecimal except special values. This property is introduced for
- * J2SE 5 compatibility support.
- *
- * @serial
- * @since ICU 3.6
- * @see #setParseBigDecimal(boolean)
- * @see #isParseBigDecimal()
- */
- private boolean parseBigDecimal = false;
-
- /**
- * The currency usage for the NumberFormat(standard or cash usage).
- * It is used as STANDARD by default
- * @since ICU 54
- */
- private CurrencyUsage currencyUsage = CurrencyUsage.STANDARD;
-
- // ----------------------------------------------------------------------
-
- static final int currentSerialVersion = 4;
-
- /**
- * The internal serial version which says which version was written Possible values
- * are:
- *
- * <ul>
- *
- * <li><b>0</b> (default): versions before JDK 1.2
- *
- * <li><b>1</b>: version from JDK 1.2 and later, which includes the two new fields
- * <code>useExponentialNotation</code> and <code>minExponentDigits</code>.
- *
- * <li><b>2</b>: version on AlphaWorks, which adds roundingMode, formatWidth, pad,
- * padPosition, exponentSignAlwaysShown, roundingIncrement.
- *
- * <li><b>3</b>: ICU 2.2. Adds currency object.
- *
- * <li><b>4</b>: ICU 54. Adds currency usage(standard vs cash)
- *
- * </ul>
- *
- * @serial
- */
- private int serialVersionOnStream = currentSerialVersion;
-
- // ----------------------------------------------------------------------
- // CONSTANTS
- // ----------------------------------------------------------------------
-
- /**
- * {@icu} Constant for {@link #getPadPosition()} and {@link #setPadPosition(int)} to
- * specify pad characters inserted before the prefix.
- *
- * @see #setPadPosition
- * @see #getPadPosition
- * @see #PAD_AFTER_PREFIX
- * @see #PAD_BEFORE_SUFFIX
- * @see #PAD_AFTER_SUFFIX
- * @stable ICU 2.0
- */
- public static final int PAD_BEFORE_PREFIX = 0;
-
- /**
- * {@icu} Constant for {@link #getPadPosition()} and {@link #setPadPosition(int)} to
- * specify pad characters inserted after the prefix.
- *
- * @see #setPadPosition
- * @see #getPadPosition
- * @see #PAD_BEFORE_PREFIX
- * @see #PAD_BEFORE_SUFFIX
- * @see #PAD_AFTER_SUFFIX
- * @stable ICU 2.0
- */
- public static final int PAD_AFTER_PREFIX = 1;
-
- /**
- * {@icu} Constant for {@link #getPadPosition()} and {@link #setPadPosition(int)} to
- * specify pad characters inserted before the suffix.
- *
- * @see #setPadPosition
- * @see #getPadPosition
- * @see #PAD_BEFORE_PREFIX
- * @see #PAD_AFTER_PREFIX
- * @see #PAD_AFTER_SUFFIX
- * @stable ICU 2.0
- */
- public static final int PAD_BEFORE_SUFFIX = 2;
-
- /**
- * {@icu} Constant for {@link #getPadPosition()} and {@link #setPadPosition(int)} to
- * specify pad characters inserted after the suffix.
- *
- * @see #setPadPosition
- * @see #getPadPosition
- * @see #PAD_BEFORE_PREFIX
- * @see #PAD_AFTER_PREFIX
- * @see #PAD_BEFORE_SUFFIX
- * @stable ICU 2.0
- */
- public static final int PAD_AFTER_SUFFIX = 3;
-
- // Constants for characters used in programmatic (unlocalized) patterns.
- static final char PATTERN_ZERO_DIGIT = '0';
- static final char PATTERN_ONE_DIGIT = '1';
- static final char PATTERN_TWO_DIGIT = '2';
- static final char PATTERN_THREE_DIGIT = '3';
- static final char PATTERN_FOUR_DIGIT = '4';
- static final char PATTERN_FIVE_DIGIT = '5';
- static final char PATTERN_SIX_DIGIT = '6';
- static final char PATTERN_SEVEN_DIGIT = '7';
- static final char PATTERN_EIGHT_DIGIT = '8';
- static final char PATTERN_NINE_DIGIT = '9';
- static final char PATTERN_GROUPING_SEPARATOR = ',';
- static final char PATTERN_DECIMAL_SEPARATOR = '.';
- static final char PATTERN_DIGIT = '#';
- static final char PATTERN_SIGNIFICANT_DIGIT = '@';
- static final char PATTERN_EXPONENT = 'E';
- static final char PATTERN_PLUS_SIGN = '+';
- static final char PATTERN_MINUS_SIGN = '-';
-
- // Affix
- private static final char PATTERN_PER_MILLE = '\u2030';
- private static final char PATTERN_PERCENT = '%';
- static final char PATTERN_PAD_ESCAPE = '*';
-
- // Other
- private static final char PATTERN_SEPARATOR = ';';
-
- // Pad escape is package private to allow access by DecimalFormatSymbols.
- // Also plus sign. Also exponent.
-
- /**
- * The CURRENCY_SIGN is the standard Unicode symbol for currency. It is used in
- * patterns and substitued with either the currency symbol, or if it is doubled, with
- * the international currency symbol. If the CURRENCY_SIGN is seen in a pattern, then
- * the decimal separator is replaced with the monetary decimal separator.
- *
- * The CURRENCY_SIGN is not localized.
- */
- private static final char CURRENCY_SIGN = '\u00A4';
-
- private static final char QUOTE = '\'';
-
- /**
- * Upper limit on integer and fraction digits for a Java double [Richard/GCL]
- */
- static final int DOUBLE_INTEGER_DIGITS = 309;
- static final int DOUBLE_FRACTION_DIGITS = 340;
-
- /**
- * When someone turns on scientific mode, we assume that more than this number of
- * digits is due to flipping from some other mode that didn't restrict the maximum,
- * and so we force 1 integer digit. We don't bother to track and see if someone is
- * using exponential notation with more than this number, it wouldn't make sense
- * anyway, and this is just to make sure that someone turning on scientific mode with
- * default settings doesn't end up with lots of zeroes.
- */
- static final int MAX_SCIENTIFIC_INTEGER_DIGITS = 8;
-
- // Proclaim JDK 1.1 serial compatibility.
- private static final long serialVersionUID = 864413376551465018L;
-
- private ArrayList<FieldPosition> attributes = new ArrayList<FieldPosition>();
-
- // The following are used in currency format
-
- // -- triple currency sign char array
- // private static final char[] tripleCurrencySign = {0xA4, 0xA4, 0xA4};
- // -- triple currency sign string
- // private static final String tripleCurrencyStr = new String(tripleCurrencySign);
- //
- // -- default currency plural pattern char array
- // private static final char[] defaultCurrencyPluralPatternChar =
- // {0, '.', '#', '#', ' ', 0xA4, 0xA4, 0xA4};
- // -- default currency plural pattern string
- // private static final String defaultCurrencyPluralPattern =
- // new String(defaultCurrencyPluralPatternChar);
-
- // pattern used in this formatter
- private String formatPattern = "";
- // style is only valid when decimal formatter is constructed by
- // DecimalFormat(pattern, decimalFormatSymbol, style)
- private int style = NumberFormat.NUMBERSTYLE;
- /**
- * Represents whether this is a currency format, and which currency format style. 0:
- * not currency format type; 1: currency style -- symbol name, such as "$" for US
- * dollar. 2: currency style -- ISO name, such as USD for US dollar. 3: currency style
- * -- plural long name, such as "US Dollar" for "1.00 US Dollar", or "US Dollars" for
- * "3.00 US Dollars".
- */
- private int currencySignCount = CURRENCY_SIGN_COUNT_ZERO;
-
- /**
- * For parsing purposes, we need to remember all prefix patterns and suffix patterns
- * of every currency format pattern, including the pattern of the default currency
- * style, ISO currency style, and plural currency style. The patterns are set through
- * applyPattern. The following are used to represent the affix patterns in currency
- * plural formats.
- */
- private static final class AffixForCurrency {
- // negative prefix pattern
- private String negPrefixPatternForCurrency = null;
- // negative suffix pattern
- private String negSuffixPatternForCurrency = null;
- // positive prefix pattern
- private String posPrefixPatternForCurrency = null;
- // positive suffix pattern
- private String posSuffixPatternForCurrency = null;
- private final int patternType;
-
- public AffixForCurrency(String negPrefix, String negSuffix, String posPrefix,
- String posSuffix, int type) {
- negPrefixPatternForCurrency = negPrefix;
- negSuffixPatternForCurrency = negSuffix;
- posPrefixPatternForCurrency = posPrefix;
- posSuffixPatternForCurrency = posSuffix;
- patternType = type;
- }
-
- public String getNegPrefix() {
- return negPrefixPatternForCurrency;
- }
-
- public String getNegSuffix() {
- return negSuffixPatternForCurrency;
- }
-
- public String getPosPrefix() {
- return posPrefixPatternForCurrency;
- }
-
- public String getPosSuffix() {
- return posSuffixPatternForCurrency;
- }
-
- public int getPatternType() {
- return patternType;
- }
- }
-
- // Affix pattern set for currency. It is a set of AffixForCurrency, each element of
- // the set saves the negative prefix, negative suffix, positive prefix, and positive
- // suffix of a pattern.
- private transient Set<AffixForCurrency> affixPatternsForCurrency = null;
-
- // For currency parsing. Since currency parsing needs to parse against all currency
- // patterns, before the parsing, we need to set up the affix patterns for all currencies.
- private transient boolean isReadyForParsing = false;
-
- // Information needed for DecimalFormat to format/parse currency plural.
- private CurrencyPluralInfo currencyPluralInfo = null;
-
- /**
- * Unit is an immutable class for the textual representation of a unit, in
- * particular its prefix and suffix.
- *
- * @author rocketman
- *
- */
- static class Unit {
- private final String prefix;
- private final String suffix;
-
- public Unit(String prefix, String suffix) {
- this.prefix = prefix;
- this.suffix = suffix;
- }
-
- public void writeSuffix(StringBuffer toAppendTo) {
- toAppendTo.append(suffix);
- }
-
- public void writePrefix(StringBuffer toAppendTo) {
- toAppendTo.append(prefix);
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (!(obj instanceof Unit)) {
- return false;
- }
- Unit other = (Unit) obj;
- return prefix.equals(other.prefix) && suffix.equals(other.suffix);
- }
- @Override
- public String toString() {
- return prefix + "/" + suffix;
- }
- }
-
- static final Unit NULL_UNIT = new Unit("", "");
-
- // Note about rounding implementation
- //
- // The original design intended to skip rounding operation when roundingIncrement is not
- // set. However, rounding may need to occur when fractional digits exceed the width of
- // fractional part of pattern.
- //
- // DigitList class has built-in rounding mechanism, using ROUND_HALF_EVEN. This implementation
- // forces non-null roundingIncrement if the setting is other than ROUND_HALF_EVEN, otherwise,
- // when rounding occurs in DigitList by pattern's fractional digits' width, the result
- // does not match the rounding mode.
- //
- // Ideally, all rounding operation should be done in one place like ICU4C trunk does
- // (ICU4C rounding implementation was rewritten recently). This is intrim implemetation
- // to fix various issues. In the future, we should entire implementation of rounding
- // in this class, like ICU4C did.
- //
- // Once we fully implement rounding logic in DigitList, then following fields and methods
- // should be gone.
-
- private transient BigDecimal actualRoundingIncrementICU = null;
- private transient java.math.BigDecimal actualRoundingIncrement = null;
-
- /*
- * The actual rounding increment as a double.
- */
- private transient double roundingDouble = 0.0;
-
- /*
- * If the roundingDouble is the reciprocal of an integer (the most common case!), this
- * is set to be that integer. Otherwise it is 0.0.
- */
- private transient double roundingDoubleReciprocal = 0.0;
-
- /*
- * Set roundingDouble, roundingDoubleReciprocal and actualRoundingIncrement
- * based on rounding mode and width of fractional digits. Whenever setting affecting
- * rounding mode, rounding increment and maximum width of fractional digits, then
- * this method must be called.
- *
- * roundingIncrementICU is the field storing the custom rounding increment value,
- * while actual rounding increment could be larger.
- */
- private void resetActualRounding() {
- if (roundingIncrementICU != null) {
- BigDecimal byWidth = getMaximumFractionDigits() > 0 ?
- BigDecimal.ONE.movePointLeft(getMaximumFractionDigits()) : BigDecimal.ONE;
- if (roundingIncrementICU.compareTo(byWidth) >= 0) {
- actualRoundingIncrementICU = roundingIncrementICU;
- } else {
- actualRoundingIncrementICU = byWidth.equals(BigDecimal.ONE) ? null : byWidth;
- }
- } else {
- if (roundingMode == BigDecimal.ROUND_HALF_EVEN || isScientificNotation()) {
- // This rounding fix is irrelevant if mode is ROUND_HALF_EVEN as DigitList
- // does ROUND_HALF_EVEN for us. This rounding fix won't work at all for
- // scientific notation.
- actualRoundingIncrementICU = null;
- } else {
- if (getMaximumFractionDigits() > 0) {
- actualRoundingIncrementICU = BigDecimal.ONE.movePointLeft(getMaximumFractionDigits());
- } else {
- actualRoundingIncrementICU = BigDecimal.ONE;
- }
- }
- }
-
- if (actualRoundingIncrementICU == null) {
- setRoundingDouble(0.0d);
- actualRoundingIncrement = null;
- } else {
- setRoundingDouble(actualRoundingIncrementICU.doubleValue());
- actualRoundingIncrement = actualRoundingIncrementICU.toBigDecimal();
- }
- }
-
- static final double roundingIncrementEpsilon = 0.000000001;
-
- private void setRoundingDouble(double newValue) {
- roundingDouble = newValue;
- if (roundingDouble > 0.0d) {
- double rawRoundedReciprocal = 1.0d / roundingDouble;
- roundingDoubleReciprocal = Math.rint(rawRoundedReciprocal);
- if (Math.abs(rawRoundedReciprocal - roundingDoubleReciprocal) > roundingIncrementEpsilon) {
- roundingDoubleReciprocal = 0.0d;
- }
- } else {
- roundingDoubleReciprocal = 0.0d;
- }
- }
-}
-
-// eof
* <p>To format a Measure object, first create a formatter
* object using a MeasureFormat factory method. Then use that
* object's format or formatMeasures methods.
- *
+ *
* Here is sample code:
* <pre>
* MeasureFormat fmtFr = MeasureFormat.getInstance(
* ULocale.FRENCH, FormatWidth.SHORT);
* Measure measure = new Measure(23, MeasureUnit.CELSIUS);
- *
+ *
* // Output: 23 °C
* System.out.println(fmtFr.format(measure));
*
*
* // Output: 70 °F
* System.out.println(fmtFr.format(measureF));
- *
+ *
* MeasureFormat fmtFrFull = MeasureFormat.getInstance(
* ULocale.FRENCH, FormatWidth.WIDE);
* // Output: 70 pieds et 5,3 pouces
* System.out.println(fmtFrFull.formatMeasures(
* new Measure(70, MeasureUnit.FOOT),
* new Measure(5.3, MeasureUnit.INCH)));
- *
+ *
* // Output: 1 pied et 1 pouce
* System.out.println(fmtFrFull.formatMeasures(
* new Measure(1, MeasureUnit.FOOT),
* new Measure(1, MeasureUnit.INCH)));
- *
+ *
* MeasureFormat fmtFrNarrow = MeasureFormat.getInstance(
ULocale.FRENCH, FormatWidth.NARROW);
* // Output: 1′ 1″
* System.out.println(fmtFrNarrow.formatMeasures(
* new Measure(1, MeasureUnit.FOOT),
* new Measure(1, MeasureUnit.INCH)));
- *
- *
+ *
+ *
* MeasureFormat fmtEn = MeasureFormat.getInstance(ULocale.ENGLISH, FormatWidth.WIDE);
- *
+ *
* // Output: 1 inch, 2 feet
* fmtEn.formatMeasures(
* new Measure(1, MeasureUnit.INCH),
* This class is immutable and thread-safe so long as its deprecated subclass,
* TimeUnitFormat, is never used. TimeUnitFormat is not thread-safe, and is
* mutable. Although this class has existing subclasses, this class does not support new
- * sub-classes.
+ * sub-classes.
*
* @see com.ibm.icu.text.UFormat
* @author Alan Liu
/**
* Formatting width enum.
- *
+ *
* @stable ICU 53
*/
// Be sure to update MeasureUnitTest.TestSerialFormatWidthEnum
/**
* Spell out everything.
- *
+ *
* @stable ICU 53
*/
- WIDE(ListFormatter.Style.DURATION, NumberFormat.PLURALCURRENCYSTYLE),
+ WIDE(ListFormatter.Style.DURATION, NumberFormat.PLURALCURRENCYSTYLE),
/**
* Abbreviate when possible.
- *
+ *
* @stable ICU 53
*/
- SHORT(ListFormatter.Style.DURATION_SHORT, NumberFormat.ISOCURRENCYSTYLE),
+ SHORT(ListFormatter.Style.DURATION_SHORT, NumberFormat.ISOCURRENCYSTYLE),
/**
* Brief. Use only a symbol for the unit when possible.
- *
+ *
* @stable ICU 53
*/
NARROW(ListFormatter.Style.DURATION_NARROW, NumberFormat.CURRENCYSTYLE),
* Identical to NARROW except when formatMeasures is called with
* an hour and minute; minute and second; or hour, minute, and second Measures.
* In these cases formatMeasures formats as 5:37:23 instead of 5h, 37m, 23s.
- *
+ *
* @stable ICU 53
*/
NUMERIC(ListFormatter.Style.DURATION_NARROW, NumberFormat.CURRENCYSTYLE);
* If the pos argument identifies a NumberFormat field,
* then its indices are set to the beginning and end of the first such field
* encountered. MeasureFormat itself does not supply any fields.
- *
+ *
* Calling a
* <code>formatMeasures</code> method is preferred over calling
* this method as they give better performance.
- *
+ *
* @param obj must be a Collection<? extends Measure>, Measure[], or Measure object.
* @param toAppendTo Formatted string appended here.
* @param pos Identifies a field in the formatted text.
* @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer, java.text.FieldPosition)
- *
+ *
* @stable ICU53
*/
@Override
} else if (obj instanceof Measure){
toAppendTo.append(formatMeasure((Measure) obj, numberFormat, new StringBuilder(), fpos));
} else {
- throw new IllegalArgumentException(obj.toString());
+ throw new IllegalArgumentException(obj.toString());
}
if (fpos.getBeginIndex() != 0 || fpos.getEndIndex() != 0) {
pos.setBeginIndex(fpos.getBeginIndex() + prevLength);
* and using the appropriate Number values. Typically the units should be
* in descending order, with all but the last Measure having integer values
* (eg, not “3.2 feet, 2 inches”).
- *
+ *
* @param measures a sequence of one or more measures.
* @return the formatted string.
* @stable ICU 53
* <br>Note: If the format doesn’t have enough decimals, or lowValue ≥ highValue,
* the result will be a degenerate range, like “5-5 meters”.
* <br>Currency Units are not yet supported.
- *
+ *
* @param lowValue low value in range
* @param highValue high value in range
* @return the formatted string.
}
final double lowDouble = lowNumber.doubleValue();
- String keywordLow = rules.select(new PluralRules.FixedDecimal(lowDouble,
+ String keywordLow = rules.select(new PluralRules.FixedDecimal(lowDouble,
lowFpos.getCountVisibleFractionDigits(), lowFpos.getFractionDigits()));
final double highDouble = highNumber.doubleValue();
- String keywordHigh = rules.select(new PluralRules.FixedDecimal(highDouble,
+ String keywordHigh = rules.select(new PluralRules.FixedDecimal(highDouble,
highFpos.getCountVisibleFractionDigits(), highFpos.getFractionDigits()));
final PluralRanges pluralRanges = Factory.getDefaultFactory().getPluralRanges(getLocale());
result.append(affix.substring(pos+replacement.length()));
}
}
-
+
/**
- * Formats a single measure per unit.
- *
+ * Formats a single measure per unit.
+ *
* An example of such a formatted string is "3.5 meters per second."
*
* @param measure the measure object. In above example, 3.5 meters.
/**
* Formats a sequence of measures.
- *
+ *
* If the fieldPosition argument identifies a NumberFormat field,
* then its indices are set to the beginning and end of the first such field
* encountered. MeasureFormat itself does not supply any fields.
- *
+ *
* @param appendTo the formatted string appended here.
* @param fieldPosition Identifies a field in the formatted text.
* @param measures the measures to format.
}
MeasureFormat rhs = (MeasureFormat) other;
// A very slow but safe implementation.
- return getWidth() == rhs.getWidth()
- && getLocale().equals(rhs.getLocale())
+ return getWidth() == rhs.getWidth()
+ && getLocale().equals(rhs.getLocale())
&& getNumberFormat().equals(rhs.getNumberFormat());
}
@Override
public final int hashCode() {
// A very slow but safe implementation.
- return (getLocale().hashCode() * 31
+ return (getLocale().hashCode() * 31
+ getNumberFormat().hashCode()) * 31 + getWidth().hashCode();
}
return pattern;
}
- /**
- * @internal
- * @deprecated This API is ICU internal only.
- */
- @Deprecated
- public String getPluralFormatter(MeasureUnit unit, FormatWidth width, int index) {
+ private String getPluralFormatter(MeasureUnit unit, FormatWidth width, int index) {
if (index != StandardPlural.OTHER_INDEX) {
String pattern = getFormatterOrNull(unit, width, index);
if (pattern != null) {
suffix = pattern.substring(pos+3);
}
}
- @Override
public String toString() {
return prefix + "; " + suffix;
}
if (fieldPositionFoundIndex == -1) {
results[i] = formatMeasure(measures[i], nf, new StringBuilder(), fpos).toString();
if (fpos.getBeginIndex() != 0 || fpos.getEndIndex() != 0) {
- fieldPositionFoundIndex = i;
+ fieldPositionFoundIndex = i;
}
} else {
results[i] = formatMeasure(measures[i], nf);
// if hour-minute-second
if (startIndex == 0 && endIndex == 2) {
return formatNumeric(
- d,
+ d,
numericFormatters.getHourMinuteSecond(),
DateFormat.Field.SECOND,
hms[endIndex],
// if minute-second
if (startIndex == 1 && endIndex == 2) {
return formatNumeric(
- d,
+ d,
numericFormatters.getMinuteSecond(),
DateFormat.Field.SECOND,
hms[endIndex],
// if hour-minute
if (startIndex == 0 && endIndex == 1) {
return formatNumeric(
- d,
+ d,
numericFormatters.getHourMinute(),
DateFormat.Field.MINUTE,
hms[endIndex],
public MeasureProxy() {
}
- @Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeByte(0); // version
out.writeUTF(locale.toLanguageTag());
out.writeObject(keyValues);
}
- @Override
@SuppressWarnings("unchecked")
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
in.readByte(); // version.
import com.ibm.icu.impl.Utility;
import com.ibm.icu.text.MessagePattern.ArgType;
import com.ibm.icu.text.MessagePattern.Part;
-import com.ibm.icu.text.PluralRules.IFixedDecimal;
+import com.ibm.icu.text.PluralRules.FixedDecimal;
import com.ibm.icu.text.PluralRules.PluralType;
import com.ibm.icu.util.ICUUncheckedIOException;
import com.ibm.icu.util.ULocale;
assert context.number.doubleValue() == number; // argument number minus the offset
context.numberString = context.formatter.format(context.number);
if(context.formatter instanceof DecimalFormat) {
- IFixedDecimal dec = ((DecimalFormat)context.formatter).getFixedDecimal(number);
+ FixedDecimal dec = ((DecimalFormat)context.formatter).getFixedDecimal(number);
return rules.select(dec);
} else {
return rules.select(number);
}
/**
- * {@icu} Sets whether strict parsing is in effect. When this is true, the string
- * is required to be a stronger match to the pattern than when lenient parsing is in
- * effect. More specifically, the following conditions cause a parse failure relative
- * to lenient mode (examples use the pattern "#,##0.#"):<ul>
- * <li>The presence and position of special symbols, including currency, must match the
- * pattern.<br>
- * '123-' fails (the minus sign is expected in the prefix, not suffix)</li>
+ * {@icu} Sets whether strict parsing is in effect. When this is true, the
+ * following conditions cause a parse failure (examples use the pattern "#,##0.#"):<ul>
* <li>Leading or doubled grouping separators<br>
* ',123' and '1,,234" fail</li>
* <li>Groups of incorrect length when grouping is used<br>
/**
* Returns the maximum number of digits allowed in the integer portion of a
* number. The default value is 40, which subclasses can override.
- *
- * When formatting, if the number of digits exceeds this value, the highest-
- * significance digits are truncated until the limit is reached, in accordance
- * with UTS#35.
- *
- * This setting has no effect on parsing.
- *
+ * When formatting, the exact behavior when this value is exceeded is
+ * subclass-specific. When parsing, this has no effect.
* @return the maximum number of integer digits
* @see #setMaximumIntegerDigits
* @stable ICU 2.0
f.setDecimalSeparatorAlwaysShown(false);
f.setParseIntegerOnly(true);
}
+
if (choice == CASHCURRENCYSTYLE) {
f.setCurrencyUsage(CurrencyUsage.CASH);
}
- if (choice == PLURALCURRENCYSTYLE) {
- f.setCurrencyPluralInfo(CurrencyPluralInfo.getInstance(desiredLocale));
- }
format = f;
}
// TODO: the actual locale of the *pattern* may differ from that
* @param choice the pattern format.
* @return the pattern
* @stable ICU 3.2
- * @internal
- * @deprecated This API is ICU internal only.
*/
- @Deprecated
- public static String getPattern(ULocale forLocale, int choice) {
+ protected static String getPattern(ULocale forLocale, int choice) {
/* for ISOCURRENCYSTYLE and PLURALCURRENCYSTYLE,
* the pattern is the same as the pattern of CURRENCYSTYLE
* but by replacing the single currency sign with
switch (choice) {
case NUMBERSTYLE:
case INTEGERSTYLE:
- case PLURALCURRENCYSTYLE:
patternKey = "decimalFormat";
break;
case CURRENCYSTYLE:
break;
case CASHCURRENCYSTYLE:
case ISOCURRENCYSTYLE:
+ case PLURALCURRENCYSTYLE:
case STANDARDCURRENCYSTYLE:
patternKey = "currencyFormat";
break;
import com.ibm.icu.impl.Utility;
import com.ibm.icu.text.PluralRules.FixedDecimal;
-import com.ibm.icu.text.PluralRules.IFixedDecimal;
-import com.ibm.icu.text.PluralRules.Operand;
import com.ibm.icu.text.PluralRules.PluralType;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.ULocale.Category;
private final class PluralSelectorAdapter implements PluralSelector {
@Override
public String select(Object context, double number) {
- IFixedDecimal dec = (IFixedDecimal) context;
- assert dec.getPluralOperand(Operand.n) == Math.abs(number);
+ FixedDecimal dec = (FixedDecimal) context;
+ assert dec.source == (dec.isNegative ? -number : number);
return pluralRules.select(dec);
}
}
} else {
numberString = numberFormat.format(numberMinusOffset);
}
- IFixedDecimal dec;
+ FixedDecimal dec;
if(numberFormat instanceof DecimalFormat) {
dec = ((DecimalFormat) numberFormat).getFixedDecimal(numberMinusOffset);
} else {
private static final long serialVersionUID = 9163464945387899416L;
@Override
- public boolean isFulfilled(IFixedDecimal n) {
+ public boolean isFulfilled(FixedDecimal n) {
return true;
}
*/
public static final PluralRules DEFAULT = new PluralRules(new RuleList().addRule(DEFAULT_RULE));
- /**
- * @internal
- * @deprecated This API is ICU internal only.
- */
- @Deprecated
- public static enum Operand {
- /** The double value of the entire number. */
+ private enum Operand {
n,
- /** The integer value, with the fraction digits truncated off. */
i,
- /** All visible fraction digits as an integer, including trailing zeros. */
f,
- /** Visible fraction digits, not including trailing zeros. */
t,
- /** Number of visible fraction digits. */
v,
w,
/* deprecated */
* @deprecated This API is ICU internal only.
*/
@Deprecated
- public static interface IFixedDecimal {
- public double getPluralOperand(Operand operand);
- public boolean isNaN();
- public boolean isInfinite();
- }
-
- /**
- * @internal
- * @deprecated This API is ICU internal only.
- */
- @Deprecated
- public static class FixedDecimal extends Number implements Comparable<FixedDecimal>, IFixedDecimal {
+ public static class FixedDecimal extends Number implements Comparable<FixedDecimal> {
private static final long serialVersionUID = -4756200506571685661L;
/**
* @internal
* @internal
* @deprecated This API is ICU internal only.
*/
- @Override
@Deprecated
- public double getPluralOperand(Operand operand) {
+ public double get(Operand operand) {
switch(operand) {
default: return source;
case i: return integerValue;
) throws IOException, ClassNotFoundException {
throw new NotSerializableException();
}
-
- /* (non-Javadoc)
- * @see com.ibm.icu.text.PluralRules.IFixedDecimal#isNaN()
- */
- @Override
- public boolean isNaN() {
- return Double.isNaN(source);
- }
-
- /* (non-Javadoc)
- * @see com.ibm.icu.text.PluralRules.IFixedDecimal#isInfinite()
- */
- @Override
- public boolean isInfinite() {
- return Double.isInfinite(source);
- }
}
/**
* Returns true if the number fulfills the constraint.
* @param n the number to test, >= 0.
*/
- boolean isFulfilled(IFixedDecimal n);
+ boolean isFulfilled(FixedDecimal n);
/*
* Returns false if an unlimited number of values fulfills the
}
@Override
- public boolean isFulfilled(IFixedDecimal number) {
- double n = number.getPluralOperand(operand);
+ public boolean isFulfilled(FixedDecimal number) {
+ double n = number.get(operand);
if ((integersOnly && (n - (long)n) != 0.0
- || operand == Operand.j && number.getPluralOperand(Operand.v) != 0)) {
+ || operand == Operand.j && number.visibleDecimalDigitCount != 0)) {
return !inRange;
}
if (mod != 0) {
}
@Override
- public boolean isFulfilled(IFixedDecimal n) {
+ public boolean isFulfilled(FixedDecimal n) {
return a.isFulfilled(n)
&& b.isFulfilled(n);
}
}
@Override
- public boolean isFulfilled(IFixedDecimal n) {
+ public boolean isFulfilled(FixedDecimal n) {
return a.isFulfilled(n)
|| b.isFulfilled(n);
}
return keyword;
}
- public boolean appliesTo(IFixedDecimal n) {
+ public boolean appliesTo(FixedDecimal n) {
return constraint.isFulfilled(n);
}
return this;
}
- private Rule selectRule(IFixedDecimal n) {
+ private Rule selectRule(FixedDecimal n) {
for (Rule rule : rules) {
if (rule.appliesTo(n)) {
return rule;
return null;
}
- public String select(IFixedDecimal n) {
- if (n.isInfinite() || n.isNaN()) {
+ public String select(FixedDecimal n) {
+ if (Double.isInfinite(n.source) || Double.isNaN(n.source)) {
return KEYWORD_OTHER;
}
Rule r = selectRule(n);
return null;
}
- public boolean select(IFixedDecimal sample, String keyword) {
+ public boolean select(FixedDecimal sample, String keyword) {
for (Rule rule : rules) {
if (rule.getKeyword().equals(keyword) && rule.appliesTo(sample)) {
return true;
}
@SuppressWarnings("unused")
- private boolean addConditional(Set<IFixedDecimal> toAddTo, Set<IFixedDecimal> others, double trial) {
+ private boolean addConditional(Set<FixedDecimal> toAddTo, Set<FixedDecimal> others, double trial) {
boolean added;
- IFixedDecimal toAdd = new FixedDecimal(trial);
+ FixedDecimal toAdd = new FixedDecimal(trial);
if (!toAddTo.contains(toAdd) && !others.contains(toAdd)) {
others.add(toAdd);
added = true;
* @deprecated This API is ICU internal only.
*/
@Deprecated
- public String select(IFixedDecimal number) {
+ public String select(FixedDecimal number) {
return rules.select(number);
}
public StringBuffer format(com.ibm.icu.math.BigDecimal number,
StringBuffer toAppendTo,
FieldPosition pos) {
- if (MIN_VALUE.compareTo(number) > 0 || MAX_VALUE.compareTo(number) < 0) {
+ if (MIN_VALUE.compareTo(number) >= 0 || MAX_VALUE.compareTo(number) <= 0) {
// We're outside of our normal range that this framework can handle.
// The DecimalFormat will provide more accurate results.
return getDecimalFormat().format(number, toAppendTo, pos);
import java.text.CharacterIterator;
import java.util.Map;
-import com.ibm.icu.impl.number.Parse;
import com.ibm.icu.lang.UCharacter;
import com.ibm.icu.util.ULocale;
int start = iterator.getRunStart(NumberFormat.Field.EXPONENT_SIGN);
int limit = iterator.getRunLimit(NumberFormat.Field.EXPONENT_SIGN);
int aChar = char32AtAndAdvance(iterator);
- if (Parse.UNISET_MINUS.contains(aChar)) {
+ if (DecimalFormat.minusSigns.contains(aChar)) {
append(
iterator,
copyFromOffset,
start,
result);
result.append(SUPERSCRIPT_MINUS_SIGN);
- } else if (Parse.UNISET_PLUS.contains(aChar)) {
+ } else if (DecimalFormat.plusSigns.contains(aChar)) {
append(
iterator,
copyFromOffset,
*/
@Deprecated
public static String parse(ULocale locale, String text, int type, ParsePosition pos) {
- List<TextTrieMap<CurrencyStringInfo>> currencyTrieVec = getCurrencyTrieVec(locale);
+ List<TextTrieMap<CurrencyStringInfo>> currencyTrieVec = CURRENCY_NAME_CACHE.get(locale);
+ if (currencyTrieVec == null) {
+ TextTrieMap<CurrencyStringInfo> currencyNameTrie =
+ new TextTrieMap<CurrencyStringInfo>(true);
+ TextTrieMap<CurrencyStringInfo> currencySymbolTrie =
+ new TextTrieMap<CurrencyStringInfo>(false);
+ currencyTrieVec = new ArrayList<TextTrieMap<CurrencyStringInfo>>();
+ currencyTrieVec.add(currencySymbolTrie);
+ currencyTrieVec.add(currencyNameTrie);
+ setupCurrencyTrieVec(locale, currencyTrieVec);
+ CURRENCY_NAME_CACHE.put(locale, currencyTrieVec);
+ }
+
int maxLength = 0;
String isoResult = null;
return isoResult;
}
- /**
- * @internal
- * @deprecated This API is ICU internal only.
- */
- @Deprecated
- public static TextTrieMap<CurrencyStringInfo>.ParseState openParseState(
- ULocale locale, int startingCp, int type) {
- List<TextTrieMap<CurrencyStringInfo>> currencyTrieVec = getCurrencyTrieVec(locale);
- if (type == Currency.LONG_NAME) {
- return currencyTrieVec.get(0).openParseState(startingCp);
- } else {
- return currencyTrieVec.get(1).openParseState(startingCp);
- }
- }
-
- private static List<TextTrieMap<CurrencyStringInfo>> getCurrencyTrieVec(ULocale locale) {
- List<TextTrieMap<CurrencyStringInfo>> currencyTrieVec = CURRENCY_NAME_CACHE.get(locale);
- if (currencyTrieVec == null) {
- TextTrieMap<CurrencyStringInfo> currencyNameTrie =
- new TextTrieMap<CurrencyStringInfo>(true);
- TextTrieMap<CurrencyStringInfo> currencySymbolTrie =
- new TextTrieMap<CurrencyStringInfo>(false);
- currencyTrieVec = new ArrayList<TextTrieMap<CurrencyStringInfo>>();
- currencyTrieVec.add(currencySymbolTrie);
- currencyTrieVec.add(currencyNameTrie);
- setupCurrencyTrieVec(locale, currencyTrieVec);
- CURRENCY_NAME_CACHE.put(locale, currencyTrieVec);
- }
- return currencyTrieVec;
- }
-
private static void setupCurrencyTrieVec(ULocale locale,
List<TextTrieMap<CurrencyStringInfo>> trieVec) {
}
}
- /**
- * @internal
- * @deprecated This API is ICU internal only.
- */
- @Deprecated
- public static final class CurrencyStringInfo {
+ private static final class CurrencyStringInfo {
private String isoCode;
private String currencyString;
*/
public Measure(Number number, MeasureUnit unit) {
if (number == null || unit == null) {
- throw new NullPointerException("Number and MeasureUnit must not be null");
+ throw new NullPointerException();
}
this.number = number;
this.unit = unit;
set pattern +0;-#
begin
format output breaks
-6 \u061C+\u0666 JK
--6 \u061C-\u0666 K
+6 \u200F+\u0666 JK
+-6 \u200F-\u0666 JK
test basic patterns
set locale fr_FR
12345 2345.000
72.1234 72.1234
-test patterns with no '0' symbols
-set locale en_US
-begin
-pattern format output breaks
-# 514.23 514
-# 0.23 0
-# 0 0
-# 1 1
-##.# 514.23 514.2
-##.# 0.23 0.2
-##.# 0 0
-##.# 1 1
-#.# 514.23 514.2
-#.# 0.23 0.2
-#.# 0 0
-#.# 1 1
-.# 514.23 514.2
-.# 0.23 .2
-.# 0 .0
-.# 1 1.0
-#. 514.23 514.
-#. 0.23 0.
-#. 0 0.
-#. 1 1.
-. 514.23 514.
-. 0.23 0.
-. 0 0.
-. 1 1.
-
-test behavior on numbers approaching zero
-set locale en
-begin
-pattern format output breaks
-#.## 0.01 0.01
-#.## 0.001 0
-#.## 0 0
-#.00 0.01 .01
-#.00 0.001 .00
-#.00 0 .00
-0.00 0.01 0.01
-0.00 0.001 0.00
-0.00 0 0.00
-
-// Not in official spec, but needed for backwards compatibility
-test patterns with leading grouping separator
-set locale en_US
-begin
-pattern format output breaks
-,##0 1234.56 1,235
-'#',## 3456 #34,56
-
-test patterns with valid and invalid quote marks
-set locale et
-begin
-pattern format output breaks
-'# 1 fail
-''# 1 '1
-'''# 1 fail
-''''# 1 ''1
-'''''# 1 fail
-'-''-'# 1 -'-1
-// K doesn't know the locale symbol for et
--'-'# 1 −-1 K
-'#'# 1 #1
-''#'' 1 '1'
-''#- 1 '1− K
-'-'#- 1 -1− K
--#'-' 1 −1- K
-
test int64
set locale en
begin
0.05E0 12301.2 1,25E4 JK
##0.000#E0 0.17 170,0E-3
// JDK doesn't support significant digits in exponents
-@@@E0 6.235 6,24E0 K
@@@E0 6235 6,24E3 K
@@@#E0 6200 6,20E3 K
@@@#E0 6201 6,201E3 K
@@@#E0 6201.7 6,202E3 K
@@@#E00 6201.7 6,202E03 K
@@@#E+00 6201.7 6,202E+03 K
-// If no zeros are specified, significant digits is fraction length plus 1
-#.##E0 52413 5,24E4
-###.##E0 52413 52,4E3 K
-#E0 52413 5,2413E4 K
-0E0 52413 5E4
-
-test scientific with grouping
-set locale en
-set pattern #,##0.000E0
-begin
-format output breaks
-// J throws an IllegalArgumentException when parsing the pattern.
-1 1.000E0 J
-11 11.00E0 J
-111 111.0E0 J
-// K doesn't print the grouping separator ("1111E0")
-1111 1,111E0 JK
-// K prints too many digits ("1.1111E4")
-11111 1.111E4 JK
-111111 11.11E4 JK
-1111111 111.1E4 JK
-11111111 1,111E4 JK
-111111111 1.111E8 JK
test percents
set locale fr
// In J ICU adds padding as if 'EUR' is only 2 chars (2 * 0xa4)
\u00a4\u00a4 **####0.00 433.0 EUR *433,00 JK
// In J ICU adds padding as if 'EUR' is only 2 chars (2 * 0xa4)
-\u00a4\u00a4 **#######0 433.0 EUR ****433 JK
+\u00a4\u00a4 **#######0 433.0 EUR *433,00 JK
test padding and currencies
begin
set format 299792458.0
begin
minIntegerDigits maxIntegerDigits minFractionDigits maxFractionDigits output breaks
-// JDK gives 2.99792458E8 (maxInt + maxFrac instead of minInt + maxFrac)
-1 1000 0 5 2.99792E8 K
// JDK gives .3E9 instead of unlimited precision.
0 1 0 0 2.99792458E8 K
1 1 0 0 3E8
// JDK gives E0 instead of allowing for unlimited precision
-// S obeys the maximum integer digits and returns .299792458E9
-0 0 0 0 2.99792458E8 KS
-// JDK and S give .299792E9
-0 1 0 5 2.9979E8 KS
+0 0 0 0 2.99792458E8 K
+// JDK gives .299792E9
+0 1 0 5 2.9979E8 K
// JDK gives 300E6
0 3 0 0 299.792458E6 K
// JDK gives 299.8E6 (maybe maxInt + maxFrac instead of minInt + maxFrac)?
4 4 0 0 2998E5
0 0 1 5 .29979E9
// JDK gives E0
-// S obeys the maximum integer digits
-0 0 1 0 2.99792458E8 KS
+0 0 1 0 2.99792458E8 K
// JDK gives .2998E9
-0 0 0 4 2.998E8 KS
-// S correctly formats this as 29.979246E7.
+0 0 0 4 2.998E8 K
// JDK uses 8 + 6 for significant digits instead of 2 + 6
-// J and C return 2.9979246E8.
-2 8 1 6 29.979246E7 CJK
+2 8 1 6 2.9979246E8 K
// Treat max int digits > 8 as being the same as min int digits.
// This behavior is not spelled out in the specification.
// JDK fails here because it tries to use 9 + 6 = 15 sig digits.
begin
minIntegerDigits maxIntegerDigits minFractionDigits maxFractionDigits output breaks
// JDK gives E0
-// S obeys the max integer digits and prints 0.299...
-0 0 0 0 2.9979245E7 KS
+0 0 0 0 2.9979245E7 K
// JDK gives .3E8
0 1 0 0 2.9979245E7 K
// JDK gives 2998E4.
set locale en
set pattern #,##0.###
begin
-format maxIntegerDigits output breaks
-123 1 3
-0 0 0
-// S ignores max integer if it is less than zero and prints "123"
-123 -2147483648 0 S
-12345 1 5
-12345 -2147483648 0 S
-5.3 1 5.3
-5.3 -2147483648 .3 S
+format maxIntegerDigits output
+123 1 3
+123 -2147483648 0
+12345 1 5
+12345 -2147483648 0
+5.3 1 5.3
+5.3 -2147483648 .3
test patterns with zero
set locale en
set format 0
begin
-pattern output breaks
+pattern output
#.# 0
#. 0.
.# .0
# 0
-#,##0.00 0.00
-#,###.00 .00
00.000E00 00.000E00
0.####E0 0E0
##0.######E000 0E000
0.001234 0.001234 K
0.0012345 0.0012345 K
0.00123456 0.0012346 K
--43 -43.0
--43.7 -43.7
+-43 -43.0 K
+-43.7 -43.7 K
-43.76 -43.76 K
-43.762 -43.762 K
-43.7626 -43.763 K
1,2345,6789 4
1,23,45,6789 4 K 2
1,23,45,6789 4 K 2 2
-123,456789 6 6 3
+123,456789 6 K 6 3
123456789 6 JK 6 4
test multiplier setters
23 -12 -276
23 -1 -23
// ICU4J and JDK throw exception on zero multiplier. ICU4C does not.
-23 0 23 JKS
+23 0 23 JK
23 1 23
23 12 276
-23 12 -276
set format 1357
begin
padCharacter formatWidth output breaks
-* 8 bill1357
+* 8 bill1357 K
* 9 *bill1357 K
^ 10 ^^bill1357 K
output breaks useScientific
186283.00
1.86E5 K 1
-186283.00 0
+186283.00 K 0
test rounding mode setters
set locale en_US
-1.49 down -1 K
1.01 up 1.5 K
1.49 down 1 K
--1.01 ceiling -1
--1.49 floor -1.5
+-1.01 ceiling -1 K
+-1.49 floor -1.5 K
test currency usage setters
+// TODO: Find a country and currency where standard and cash differ
set locale CH
+set currency CHF
set pattern \u00a4\u00a4 0
begin
-format currency currencyUsage output breaks
-0.37 CHF standard CHF 0.37 K
-0.37 CHF cash CHF 0.35 CK
-1.234 CZK standard CZK 1.23 K
-1.234 CZK cash CZK 1
-
-test currency usage to pattern
-set locale en
-begin
-currency currencyUsage toPattern breaks
-// These work in J, but it prepends an extra hash sign to the pattern.
-// K does not support this feature.
-USD standard 0.00 JK
-CHF standard 0.00 JK
-CZK standard 0.00 JK
-USD cash 0.00 JK
-CHF cash 0.05 JK
-CZK cash 0 JK
-
-test currency rounding
-set locale en
-set currency USD
-begin
-pattern format output breaks
-# 123 123 S
-// Currency rounding should always override the pattern.
-// K prints the currency in ISO format for some reason.
-\u00a4# 123 $123.00 K
-\u00a4#.000 123 $123.00 K
-\u00a4#.## 123 $123.00 K
+format currencyUsage output breaks
+0.37 standard CHF 0.37 K
+// TODO: Get the rounding data into ICU4C and ICU4J
+0.37 cash CHF 0.35 CJK
test exponent parameter setters
set locale en_US
decimalSeparatorAlwaysShown exponentSignAlwaysShown minimumExponentDigits output breaks
0 0 2 3E08 K
0 1 3 3E+008 K
+// ICU DecimalFormat J does not honor decimalSeparatorAlwaysShown
+// for scientific notation. But JDK DecimalFormat does honor
// decimalSeparatorAlwaysShown K=JDK; C=ICU4C; J=ICU4J
// See ticket 11621
-1 0 2 3.E08 K
-1 1 3 3.E+008 K
+1 0 2 3.E08 JK
+1 1 3 3.E+008 JK
1 0 1 3.E8
0 0 1 3E8
// decimalSeparatorAlwaysShown off by default
299792458 3E8
299000000 2.99E8
-299792458 3.E8 1
+299792458 3.E8 J 1
test pad position setters
set locale en_US
set pattern [0.00];(#)
begin
format output breaks
-Inf [\u221e]
+Inf [\u221e] K
-Inf (\u221e) K
NaN NaN K
locale pattern format output breaks
en #0% 0.4376 44%
// This next test breaks JDK. JDK doesn't multiply by 100.
-fa \u0025\u00a0\u0023\u0030 0.4376 \u200e\u066a\u00a0\u06f4\u06f4 K
+// It also is now broken in ICU4J until #10368 is fixed.
+fa \u0025\u00a0\u0023\u0030 0.4376 \u200e\u066a\u00a0\u06f4\u06f4 JK
test toPattern
set locale en
begin
pattern toPattern breaks
-// All of the "S" failures in this section are because of functionally equivalent patterns
// JDK doesn't support any patterns with padding or both negative prefix and suffix
// Breaks ICU4J See ticket 11671
**0,000 **0,000 JK
**##0,000 **##0,000 K
**###0,000 **###0,000 K
-**####0,000 **#,##0,000 KS
+**####0,000 **#,##0,000 K
###,000. #,000.
-0,000 #0,000 S
+0,000 #0,000
.00 #.00
-000 #000 S
-000,000 #,000,000 S
+000 #000
+000,000 #,000,000
pp#,000 pp#,000
-00.## #00.## S
+00.## #00.##
#,#00.025 #,#00.025
// No secondary grouping in JDK
#,##,###.02500 #,##,###.02500 K
pp#,000;(#) pp#,000;(#,000) K
-**####,##,##0.0##;(#) **#,##,##,##0.0##;**(##,##,##0.0##) KS
+**####,##,##0.0##;(#) **#,##,##,##0.0##;**(##,##,##0.0##) K
// No significant digits in JDK
@@### @@### K
@,@#,### @,@#,### K
0.00E0 0.00E0
-// The following one works in JDK, probably because
-// it just returns the same string
-@@@##E0 @@@##E0
+@@@##E0 @@@##E0 K
###0.00#E0 ###0.00#E0
##00.00#E0 ##00.00#E0
0.00E+00 0.00E+00 K
// J requires prefix and suffix for lenient parsing, but C doesn't
5,347.25 5347.25 JK
(5,347.25 -5347.25 J
-// S is successful at parsing this as -5347.25 in lenient mode
--5,347.25 fail S
+-5,347.25 fail
+3.52E4 35200
(34.8E-3) -0.0348
// JDK stops parsing at the spaces. JDK doesn't see space as a grouping separator
// J doesn't allow trailing separators before E but C does
(34,,25,E-1) -342.5 J
(34 25 E-1) -342.5 JK
-(34,,25 E-1) -342.5 JK
+(34,,25 E-1) -3425 J
// Spaces are not allowed after exponent symbol
// C parses up to the E but J bails
(34 25E -1) -3425 JK
set pattern #,##0.0###+;#-
begin
parse output breaks
-// C sees this as -3426, don't understand why.
-// J and K just bail.
-3426 3426 JKC
+// C sees this as -3426, don't understand why
+3426 -3426 JK
3426+ 3426
-// J bails; C and K see -34
-34 d1+ 34 JKC
+// J bails, but JDK will parse up to the space and get 34.
+// C sees -34
+34 d1+ -34 JK
// JDK sees this as -1234 for some reason
// J bails b/c of trailing separators
// C parses until trailing separators, but sees -1234
-1,234,,,+ 1234 JKC
+1,234,,,+ -1234 JK
1,234- -1234
// J bails because of trailing separators
1,234,- -1234 J
test parse strict
set locale en
-set pattern +#,##,##0.0###;(#)
+set pattern +#,##0.0###;(#)
set lenient 0
-set minGroupingDigits 2
begin
parse output breaks
+123d5 123
+5347.25 5347.25
// separators in wrong place cause failure, no separators ok.
-+65,347.25 65347.25
-(65347.25) -65347.25
-(65,347.25) -65347.25
++5,347.25 5347.25
+(5347.25) -5347.25
+(5,347.25) -5347.25
// JDK does allow separators in the wrong place and parses as -5347.25
(53,47.25) fail K
// strict requires prefix or suffix
-65,347.25 fail
+5,347.25 fail
+3.52E4 35200
(34.8E-3) -0.0348
(3425E-1) -342.5
// Strict doesn't allow separators in sci notation.
-(63,425) -63425
-// JDK and S allow separators in sci notation and parses as -342.5
-(63,425E-1) fail KS
+(3,425) -3425
+// JDK allows separators in sci notation and parses as -342.5
+(3,425E-1) fail K
// Both prefix and suffix needed for strict.
// JDK accepts this and parses as -342.5
(3425E-1 fail K
+3.52EE4 3.52
-+12,34,567.8901 1234567.8901
++1,234,567.8901 1234567.8901
// With strict digit separators don't have to be the right type
// JDK doesn't acknowledge space as a separator
-+12 34 567.8901 1234567.8901 K
++1 234 567.8901 1234567.8901 K
// In general the grouping separators have to match their expected
// location exactly. The only exception is when string being parsed
// have no separators at all.
-+12,345.67 12345.67
-// JDK doesn't require separators to be in the right place.
++1,234,567.8901 1234567.8901
+// JDK doesn't require separators to be in the right place
+1,23,4567.8901 fail K
-+1,234,567.8901 fail K
+1234,567.8901 fail K
+1,234567.8901 fail K
+1234567.8901 1234567.8901
-// Minimum grouping is not satisfied below, but that's ok
-// because minimum grouping is optional.
-+1,234.5 1234.5
// Comma after decimal means parse to a comma
-+1,23,456.78,9 123456.78
-// J fails upon seeing the second decimal point
-+1,23,456.78.9 123456.78 J
++123,456.78,9 123456.78
+// A decimal after a decimal means bail
+// JDK parses as 123456.78
++123,456.78.9 fail K
+79 79
+79 79
+ 79 fail
// JDK parses as -1945
(1,945d1) fail K
-test parse strict without prefix/suffix
-set locale en
-set pattern #
-set lenient 0
-begin
-parse output breaks
-12.34 12.34
--12.34 -12.34
-+12.34 12.34 JK
-$12.34 fail
-
test parse integer only
set locale en
set pattern 0.00
begin
parse output breaks
35 35
-// S accepts leading plus signs
-+35 35 CJK
++35 fail
-35 -35
2.63 2
-39.99 -39
set locale en
begin
parse output outputCurrency breaks
-// Fixed in ticket 11735
-53.45 fail USD
+// See ticket 11735
+53.45 fail USD J
test parse strange prefix
set locale en
set negativeSuffix 9N
begin
parse output breaks
-// S is the only implementation that passes these cases.
// C consumes the '9' as a digit and assumes number is negative
// J and JDK bail
-6549K 654 CJK
+// 6549K 654 CJK
// C consumes the '9' as a digit and assumes number is negative
// J and JDK bail
-6549N -654 CJK
+// 6549N -654 CJK
test really strange prefix
set locale en
8245 45
2845 -45
-test parse pattern with quotes
-set locale en
-set pattern '-'#y
-begin
-parse output
--45y 45
-
-test parse with locale symbols
-// The grouping separator in it_CH is an apostrophe
-set locale it_CH
-set pattern #
-begin
-parse output breaks
-१३ 13
-१३.३१ 13.31
-// J and K stop parsing at the apostrophe
-123'456 123456 JK
-524'1.3 5241.3 JK
-३'१ 31 JK
-
-test parse with European-style comma/period
-set locale pt
-set pattern #
-begin
-parse output breaks
-// J and K get 123
-123.456 123456 JK
-123,456 123.456
-987,654.321 987.654
-987,654 321 987.654
-// J and K get 987
-987.654,321 987654.321 JK
-
test select
set locale sr
begin
test parse currency ISO
set pattern 0.00 \u00a4\u00a4;(#) \u00a4\u00a4
-set locale en_GB
+set locale en_US
begin
parse output outputCurrency breaks
-53.45 fail GBP
-£53.45 53.45 GBP
-$53.45 fail USD
+$53.45 53.45 USD
53.45 USD 53.45 USD
-53.45 GBP 53.45 GBP
USD 53.45 53.45 USD J
-53.45USD 53.45 USD CJ
+53.45USD fail USD
USD53.45 53.45 USD
(7.92) USD -7.92 USD
-(7.92) GBP -7.92 GBP
+(7.92) EUR -7.92 EUR
(7.926) USD -7.926 USD
-(7.926 USD) -7.926 USD CJ
-(USD 7.926) -7.926 USD CJ
-USD (7.926) -7.926 USD CJ
-USD (7.92) -7.92 USD CJ
-(7.92)USD -7.92 USD CJ
-USD(7.92) -7.92 USD CJ
+(7.926 USD) fail USD
+(USD 7.926) fail USD
+USD (7.926) fail USD
+USD (7.92) fail USD
+(7.92)USD fail USD
+USD(7.92) fail USD
(8) USD -8 USD
--8 USD -8 USD CJ
+-8 USD fail USD
67 USD 67 USD
53.45$ fail USD
US Dollars 53.45 53.45 USD J
US Dollar 53.45 53.45 USD J
53.45 US Dollar 53.45 USD
US Dollars53.45 53.45 USD
-53.45US Dollars 53.45 USD CJ
+53.45US Dollars fail USD
US Dollar53.45 53.45 USD
US Dollat53.45 fail USD
-53.45US Dollar 53.45 USD CJ
-US Dollars (53.45) -53.45 USD CJ
+53.45US Dollar fail USD
+US Dollars (53.45) fail USD
(53.45) US Dollars -53.45 USD
-(53.45) Euros -53.45 EUR
-US Dollar (53.45) -53.45 USD CJ
+US Dollar (53.45) fail USD
(53.45) US Dollar -53.45 USD
-US Dollars(53.45) -53.45 USD CJ
-(53.45)US Dollars -53.45 USD CJ
-US Dollar(53.45) -53.45 USD CJ
+US Dollars(53.45) fail USD
+(53.45)US Dollars fail USD
+US Dollar(53.45) fail USD
US Dollat(53.45) fail USD
-(53.45)US Dollar -53.45 USD CJ
+(53.45)US Dollar fail USD
test parse currency ISO negative
set pattern 0.00 \u00a4\u00a4;-# \u00a4\u00a4
-set locale en_GB
+set locale en_US
begin
parse output outputCurrency breaks
-53.45 fail GBP
-£53.45 53.45 GBP
-$53.45 fail USD
+$53.45 53.45 USD
53.45 USD 53.45 USD
-53.45 GBP 53.45 GBP
USD 53.45 53.45 USD J
-53.45USD 53.45 USD CJ
+53.45USD fail USD
USD53.45 53.45 USD
-7.92 USD -7.92 USD
--7.92 GBP -7.92 GBP
+-7.92 EUR -7.92 EUR
-7.926 USD -7.926 USD
-USD -7.926 -7.926 USD CJ
--7.92USD -7.92 USD CJ
-USD-7.92 -7.92 USD CJ
+USD -7.926 fail USD
+-7.92USD fail USD
+USD-7.92 fail USD
-8 USD -8 USD
67 USD 67 USD
53.45$ fail USD
US Dollar 53.45 53.45 USD J
53.45 US Dollar 53.45 USD
US Dollars53.45 53.45 USD
-53.45US Dollars 53.45 USD CJ
+53.45US Dollars fail USD
US Dollar53.45 53.45 USD
US Dollat53.45 fail USD
-53.45US Dollar 53.45 USD CJ
+53.45US Dollar fail USD
test parse currency long
set pattern 0.00 \u00a4\u00a4\u00a4;(#) \u00a4\u00a4\u00a4
-set locale en_GB
+set locale en_US
begin
parse output outputCurrency breaks
-// J throws a NullPointerException on the first case
-53.45 fail GBP
-£53.45 53.45 GBP
-$53.45 fail USD
+$53.45 53.45 USD
53.45 USD 53.45 USD
-53.45 GBP 53.45 GBP
USD 53.45 53.45 USD J
-53.45USD 53.45 USD CJ
+// See ticket 11735
+53.45USD fail USD J
USD53.45 53.45 USD
(7.92) USD -7.92 USD
-(7.92) GBP -7.92 GBP
(7.926) USD -7.926 USD
-(7.926 USD) -7.926 USD CJ
-(USD 7.926) -7.926 USD CJ
-USD (7.926) -7.926 USD CJ
-USD (7.92) -7.92 USD CJ
-(7.92)USD -7.92 USD CJ
-USD(7.92) -7.92 USD CJ
+(7.926 USD) fail USD
+(USD 7.926) fail USD
+USD (7.926) fail USD
+USD (7.92) fail USD
+(7.92)USD fail USD
+USD(7.92) fail USD
(8) USD -8 USD
--8 USD -8 USD CJ
+// See ticket 11735
+-8 USD fail USD J
67 USD 67 USD
-// J throws a NullPointerException on the next case
-53.45$ fail USD
+// See ticket 11735
+53.45$ fail USD J
US Dollars 53.45 53.45 USD J
53.45 US Dollars 53.45 USD
US Dollar 53.45 53.45 USD J
53.45 US Dollar 53.45 USD
US Dollars53.45 53.45 USD
-53.45US Dollars 53.45 USD CJ
+// See ticket 11735
+53.45US Dollars fail USD J
US Dollar53.45 53.45 USD
US Dollat53.45 fail USD
-53.45US Dollar 53.45 USD CJ
+// See ticket 11735
+53.45US Dollar fail USD J
test parse currency short
set pattern 0.00 \u00a4;(#) \u00a4
-set locale en_GB
+set locale en_US
begin
parse output outputCurrency breaks
-53.45 fail GBP
-£53.45 53.45 GBP
-$53.45 fail USD
+$53.45 53.45 USD
53.45 USD 53.45 USD
-53.45 GBP 53.45 GBP
USD 53.45 53.45 USD J
-53.45USD 53.45 USD CJ
+53.45USD fail USD
USD53.45 53.45 USD
(7.92) USD -7.92 USD
-(7.92) GBP -7.92 GBP
(7.926) USD -7.926 USD
-(7.926 USD) -7.926 USD CJ
-(USD 7.926) -7.926 USD CJ
-USD (7.926) -7.926 USD CJ
-USD (7.92) -7.92 USD CJ
-(7.92)USD -7.92 USD CJ
-USD(7.92) -7.92 USD CJ
+(7.926 USD) fail USD
+(USD 7.926) fail USD
+USD (7.926) fail USD
+USD (7.92) fail USD
+(7.92)USD fail USD
+USD(7.92) fail USD
(8) USD -8 USD
--8 USD -8 USD CJ
+-8 USD fail USD
67 USD 67 USD
53.45$ fail USD
US Dollars 53.45 53.45 USD J
US Dollar 53.45 53.45 USD J
53.45 US Dollar 53.45 USD
US Dollars53.45 53.45 USD
-53.45US Dollars 53.45 USD CJ
+53.45US Dollars fail USD
US Dollar53.45 53.45 USD
US Dollat53.45 fail USD
-53.45US Dollar 53.45 USD CJ
+53.45US Dollar fail USD
test parse currency short prefix
set pattern \u00a40.00;(\u00a4#)
-set locale en_GB
+set locale en_US
begin
parse output outputCurrency breaks
-53.45 fail GBP
-£53.45 53.45 GBP
-$53.45 fail USD
-53.45 USD 53.45 USD CJ
-53.45 GBP 53.45 GBP CJ
+$53.45 53.45 USD
+53.45 USD fail USD
USD 53.45 53.45 USD J
-53.45USD 53.45 USD CJ
+53.45USD fail USD
USD53.45 53.45 USD
-// S fails these because '(' is an incomplete prefix.
-(7.92) USD -7.92 USD CJS
-(7.92) GBP -7.92 GBP CJS
-(7.926) USD -7.926 USD CJS
-(7.926 USD) -7.926 USD CJS
+(7.92) USD fail USD
+(7.926) USD fail USD
+(7.926 USD) fail USD
(USD 7.926) -7.926 USD J
-USD (7.926) -7.926 USD CJS
-USD (7.92) -7.92 USD CJS
-(7.92)USD -7.92 USD CJS
-USD(7.92) -7.92 USD CJS
-(8) USD -8 USD CJS
--8 USD -8 USD CJ
-67 USD 67 USD CJ
+USD (7.926) fail USD
+USD (7.92) fail USD
+(7.92)USD fail USD
+USD(7.92) fail USD
+(8) USD fail USD
+-8 USD fail USD
+67 USD fail USD
53.45$ fail USD
US Dollars 53.45 53.45 USD J
53.45 US Dollars 53.45 USD
US Dollar 53.45 53.45 USD J
53.45 US Dollar 53.45 USD
US Dollars53.45 53.45 USD
-53.45US Dollars 53.45 USD CJ
+53.45US Dollars fail USD
US Dollar53.45 53.45 USD
-53.45US Dollar 53.45 USD CJ
+53.45US Dollar fail USD
test format foreign currency
set locale fa_IR
-set currency IRR
begin
pattern format output breaks
\u00a4\u00a4\u00a4 0.00;\u00a4\u00a4\u00a4 # 1235 \u0631\u06cc\u0627\u0644 \u0627\u06cc\u0631\u0627\u0646 \u06F1\u06F2\u06F3\u06F5 K
Euro 7.82 7.82 EUR
Euros 7.82 7.82 EUR
-test parse currency without currency mode
-// Should accept a symbol associated with the currency specified by the API,
-// but should not traverse the full currency data.
-set locale en_US
-set pattern \u00a4#,##0.00
-begin
-parse currency output breaks
-$52.41 USD 52.41
-USD52.41 USD 52.41 K
-\u20ac52.41 USD fail
-EUR52.41 USD fail
-$52.41 EUR fail
-USD52.41 EUR fail
-\u20ac52.41 EUR 52.41 K
-EUR52.41 EUR 52.41
-
test parse currency ISO strict
set pattern 0.00 \u00a4\u00a4;(#) \u00a4\u00a4
set locale en_US
format output breaks
-0.99 -0 JK
-test parse decimalPatternMatchRequired
-set locale en
-set decimalPatternMatchRequired 1
-begin
-pattern parse output breaks
-// K doesn't support this feature.
-0 123 123
-0 123. fail JK
-0 1.23 fail JK
-0 -513 -513
-0 -513. fail JK
-0 -5.13 fail JK
-0.0 123 fail K
-0.0 123. 123
-0.0 1.23 1.23
-0.0 -513 fail K
-0.0 -513. -513
-0.0 -5.13 -5.13
-
-test parse minus sign
-set locale en
-set pattern #
-begin
-parse output breaks
--123 -123
-- 123 -123 JK
- -123 -123 JK
- - 123 -123 JK
-123- -123 JK
-123 - -123 JK
-
-test parse case sensitive
-set locale en
-set lenient 1
-set pattern Aa#
-begin
-parse parseCaseSensitive output breaks
-Aa1.23 1 1.23
-Aa1.23 0 1.23
-AA1.23 1 fail
-// J and K do not support case-insensitive parsing for prefix/suffix.
-// J supports it for the exponent separator, but not K.
-AA1.23 0 1.23 JK
-aa1.23 1 fail
-aa1.23 0 1.23 JK
-Aa1.23E3 1 1230
-Aa1.23E3 0 1230
-Aa1.23e3 1 1.23 J
-Aa1.23e3 0 1230 K
-NaN 1 NaN K
-NaN 0 NaN K
-nan 1 fail
-nan 0 NaN JK
-
-test parse infinity and scientific notation overflow
-set locale en
-begin
-parse output breaks
-NaN NaN K
-// JDK returns zero
-1E999999999999999 Inf K
--1E999999999999999 -Inf K
-1E-99999999999999 0.0
-// Note: The test suite code doesn't properly check for 0.0 vs. -0.0
--1E-99999999999999 -0.0
-1E2147483648 Inf K
-1E2147483647 Inf K
-1E2147483646 1E2147483646
-1E-2147483649 0
-1E-2147483648 0
-// S returns zero here
-1E-2147483647 1E-2147483647 S
-1E-2147483646 1E-2147483646
-
-test format push limits
-set locale en
-set minFractionDigits 2
-set roundingMode halfDown
-begin
-maxFractionDigits format output breaks
-100 987654321987654321 987654321987654321.00
-100 987654321.987654321 987654321.987654321
-100 9999999999999.9950000000001 9999999999999.9950000000001
-2 9999999999999.9950000000001 10000000000000.00
-2 9999999.99499999 9999999.99
-// K doesn't support halfDowm rounding mode?
-2 9999999.995 9999999.99 K
-2 9999999.99500001 10000000.00
-100 56565656565656565656565656565656565656565656565656565656565656 56565656565656565656565656565656565656565656565656565656565656.00
-100 454545454545454545454545454545.454545454545454545454545454545 454545454545454545454545454545.454545454545454545454545454545
-100 0.0000000000000000000123 0.0000000000000000000123
-100 -78787878787878787878787878787878 -78787878787878787878787878787878.00
-100 -8989898989898989898989.8989898989898989 -8989898989898989898989.8989898989898989
-
-test ticket 11230
-set locale en
-set pattern ###
-begin
-parse output breaks
-// K and J return null; S returns 99
- 9 9 9 JKS
-// K and J return null
- 9 999 9999 JK
-
DecimalFormatSymbols US = new DecimalFormatSymbols(Locale.US);
DecimalFormat f = new DecimalFormat("#,##,###", US);
expect(f, new Long(123456789), "12,34,56,789");
- expectPat(f, "#,##,##0");
+ expectPat(f, "#,##,###");
f.applyPattern("#,###");
f.setSecondaryGroupingSize(4);
expect(f, new Long(123456789), "12,3456,789");
- expectPat(f, "#,####,##0");
+ expectPat(f, "#,####,###");
// On Sun JDK 1.2-1.3, the hi_IN locale uses '0' for a zero digit,
// but on IBM JDK 1.2-1.3, the locale uses U+0966.
fmt.setFormatWidth(16);
// 12 34567890123456
- expectPat(fmt, "AA*^#####,##0.00ZZ");
+ expectPat(fmt, "AA*^#,###,##0.00ZZ");
}
private void expectPat(DecimalFormat fmt, String exp) {
expect(new DecimalFormat[] { new DecimalFormat("#E0", US),
new DecimalFormat("##E0", US),
new DecimalFormat("####E0", US),
- new DecimalFormat("0E0", US),
- new DecimalFormat("00E0", US),
- new DecimalFormat("000E0", US),
+ new DecimalFormat("0E0", US),
+ new DecimalFormat("00E0", US),
+ new DecimalFormat("000E0", US),
},
new Long(45678000),
new String[] { "4.5678E7",
"45.678E6",
"4567.8E4",
"5E7",
- "46E6",
+ "46E6",
"457E5",
}
);
new Long(-1000000000), "(1,000,000,000.00)",
});
}
-
+
private void expect(NumberFormat fmt, Object[] data) {
for (int i=0; i<data.length; i+=2) {
expect(fmt, (Number) data[i], (String) data[i+1]);
}
}
-
+
private void expect(Object fmto, Object numo, Object expo) {
NumberFormat fmt = null, fmts[] = null;
Number num = null, nums[] = null;
if (!s.equals("-0.1")) {
errln("FAIL");
}
- }
+ }
@Test
public void TestBigDecimalJ28() {
import java.text.CharacterIterator;
import java.text.FieldPosition;
import java.text.ParsePosition;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
import java.util.Locale;
+import java.util.Map;
import org.junit.Test;
import com.ibm.icu.dev.test.TestFmwk;
import com.ibm.icu.text.CompactDecimalFormat;
import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
-import com.ibm.icu.text.DecimalFormat;
+import com.ibm.icu.text.DecimalFormatSymbols;
import com.ibm.icu.text.NumberFormat;
+import com.ibm.icu.text.PluralRules;
import com.ibm.icu.util.Currency;
import com.ibm.icu.util.CurrencyAmount;
import com.ibm.icu.util.ULocale;
{1234567890123f, "1.2T"},
{12345678901234f, "12T"},
{123456789012345f, "120T"},
- {12345678901234567890f, "12,000,000T"},
+ {12345678901234567890f, "12000000T"},
};
Object[][] SerbianTestDataShort = {
- {1, "1"},
- {12, "12"},
- {123, "120"},
+ {1234, "1,2\u00A0\u0445\u0438\u0459."},
{12345, "12\u00a0хиљ."},
{20789, "21\u00a0хиљ."},
{123456, "120\u00a0хиљ."},
};
Object[][] SerbianTestDataLong = {
- {1, "1"},
- {12, "12"},
- {123, "120"},
{1234, "1,2 хиљаде"},
{12345, "12 хиљада"},
{21789, "22 хиљаде"},
};
Object[][] SerbianTestDataLongNegative = {
- {-1, "-1"},
- {-12, "-12"},
- {-123, "-120"},
{-1234, "-1,2 хиљаде"},
{-12345, "-12 хиљада"},
{-21789, "-22 хиљаде"},
};
Object[][] JapaneseTestData = {
- {1f, "1"},
- {12f, "12"},
- {123f, "120"},
{1234f, "1200"},
{12345f, "1.2万"},
{123456f, "12万"},
};
Object[][] ChineseCurrencyTestData = {
- {new CurrencyAmount(1f, Currency.getInstance("CNY")), "¥1"},
- {new CurrencyAmount(12f, Currency.getInstance("CNY")), "¥12"},
- {new CurrencyAmount(123f, Currency.getInstance("CNY")), "¥120"},
+ // The first one should really have a ¥ in front, but the CLDR data is
+ // incorrect. See http://unicode.org/cldr/trac/ticket/9298 and update
+ // this test case when the CLDR ticket is fixed.
{new CurrencyAmount(1234f, Currency.getInstance("CNY")), "¥1.2千"},
{new CurrencyAmount(12345f, Currency.getInstance("CNY")), "¥1.2万"},
{new CurrencyAmount(123456f, Currency.getInstance("CNY")), "¥12万"},
{new CurrencyAmount(123456789012345f, Currency.getInstance("CNY")), "¥120兆"},
};
Object[][] GermanCurrencyTestData = {
- {new CurrencyAmount(1f, Currency.getInstance("EUR")), "1 €"},
- {new CurrencyAmount(12f, Currency.getInstance("EUR")), "12 €"},
- {new CurrencyAmount(123f, Currency.getInstance("EUR")), "120 €"},
{new CurrencyAmount(1234f, Currency.getInstance("EUR")), "1,2 Tsd. €"},
{new CurrencyAmount(12345f, Currency.getInstance("EUR")), "12 Tsd. €"},
{new CurrencyAmount(123456f, Currency.getInstance("EUR")), "120 Tsd. €"},
{new CurrencyAmount(123456789012345f, Currency.getInstance("EUR")), "120 Bio. €"},
};
Object[][] EnglishCurrencyTestData = {
- {new CurrencyAmount(1f, Currency.getInstance("USD")), "$1"},
- {new CurrencyAmount(12f, Currency.getInstance("USD")), "$12"},
- {new CurrencyAmount(123f, Currency.getInstance("USD")), "$120"},
{new CurrencyAmount(1234f, Currency.getInstance("USD")), "$1.2K"},
{new CurrencyAmount(12345f, Currency.getInstance("USD")), "$12K"},
{new CurrencyAmount(123456f, Currency.getInstance("USD")), "$120K"},
};
Object[][] SwahiliTestData = {
- {1f, "1"},
- {12f, "12"},
- {123f, "120"},
{1234f, "elfu\u00a01.2"},
{12345f, "elfu\u00a012"},
{123456f, "elfu\u00A0120"},
{123456789012f, "B120"},
{1234567890123f, "T1.2"},
{12345678901234f, "T12"},
- {12345678901234567890f, "T12,000,000"},
+ {12345678901234567890f, "T12000000"},
};
Object[][] CsTestDataShort = {
- {1, "1"},
- {12, "12"},
- {123, "120"},
{1000, "1\u00a0tis."},
{1500, "1,5\u00a0tis."},
{5000, "5\u00a0tis."},
};
Object[][] SwahiliTestDataNegative = {
- {-1f, "-1"},
- {-12f, "-12"},
- {-123f, "-120"},
- {-1234f, "-elfu\u00a01.2"},
- {-12345f, "-elfu\u00a012"},
- {-123456f, "-elfu\u00A0120"},
- {-1234567f, "-M1.2"},
- {-12345678f, "-M12"},
- {-123456789f, "-M120"},
- {-1234567890f, "-B1.2"},
- {-12345678901f, "-B12"},
- {-123456789012f, "-B120"},
- {-1234567890123f, "-T1.2"},
- {-12345678901234f, "-T12"},
- {-12345678901234567890f, "-T12,000,000"},
+ {-1234f, "elfu\u00a0-1.2"},
+ {-12345f, "elfu\u00a0-12"},
+ {-123456f, "elfu\u00A0-120"},
+ {-1234567f, "M-1.2"},
+ {-12345678f, "M-12"},
+ {-123456789f, "M-120"},
+ {-1234567890f, "B-1.2"},
+ {-12345678901f, "B-12"},
+ {-123456789012f, "B-120"},
+ {-1234567890123f, "T-1.2"},
+ {-12345678901234f, "T-12"},
+ {-12345678901234567890f, "T-12000000"},
};
Object[][] TestACoreCompactFormatList = {
{2000, "2Ks$s"},
};
- // TODO(sffc): Re-write these tests for the new CompactDecimalFormat pipeline
-
-// @Test
-// public void TestACoreCompactFormat() {
-// Map<String,String[][]> affixes = new HashMap();
-// affixes.put("one", new String[][] {
-// {"","",}, {"","",}, {"","",},
-// {"","K"}, {"","K"}, {"","K"},
-// {"","M"}, {"","M"}, {"","M"},
-// {"","B"}, {"","B"}, {"","B"},
-// {"","T"}, {"","T"}, {"","T"},
-// });
-// affixes.put("other", new String[][] {
-// {"","",}, {"","",}, {"","",},
-// {"","Ks"}, {"","Ks"}, {"","Ks"},
-// {"","Ms"}, {"","Ms"}, {"","Ms"},
-// {"","Bs"}, {"","Bs"}, {"","Bs"},
-// {"","Ts"}, {"","Ts"}, {"","Ts"},
-// });
-//
-// Map<String,String[]> currencyAffixes = new HashMap();
-// currencyAffixes.put("one", new String[] {"", "$"});
-// currencyAffixes.put("other", new String[] {"", "$s"});
-//
-// long[] divisors = new long[] {
-// 0,0,0,
-// 1000, 1000, 1000,
-// 1000000, 1000000, 1000000,
-// 1000000000L, 1000000000L, 1000000000L,
-// 1000000000000L, 1000000000000L, 1000000000000L};
-// long[] divisors_err = new long[] {
-// 0,0,0,
-// 13, 13, 13,
-// 1000000, 1000000, 1000000,
-// 1000000000L, 1000000000L, 1000000000L,
-// 1000000000000L, 1000000000000L, 1000000000000L};
-// checkCore(affixes, null, divisors, TestACoreCompactFormatList);
-// checkCore(affixes, currencyAffixes, divisors, TestACoreCompactFormatListCurrency);
-// try {
-// checkCore(affixes, null, divisors_err, TestACoreCompactFormatList);
-// } catch(AssertionError e) {
-// // Exception expected, thus return.
-// return;
-// }
-// fail("Error expected but passed");
-// }
-
-// private void checkCore(Map<String, String[][]> affixes, Map<String, String[]> currencyAffixes, long[] divisors, Object[][] testItems) {
-// Collection<String> debugCreationErrors = new LinkedHashSet();
-// CompactDecimalFormat cdf = new CompactDecimalFormat(
-// "#,###.00",
-// DecimalFormatSymbols.getInstance(new ULocale("fr")),
-// CompactStyle.SHORT, PluralRules.createRules("one: j is 1 or f is 1"),
-// divisors, affixes, currencyAffixes,
-// debugCreationErrors
-// );
-// if (debugCreationErrors.size() != 0) {
-// for (String s : debugCreationErrors) {
-// errln("Creation error: " + s);
-// }
-// } else {
-// checkCdf("special cdf ", cdf, testItems);
-// }
-// }
+ @Test
+ public void TestACoreCompactFormat() {
+ Map<String,String[][]> affixes = new HashMap();
+ affixes.put("one", new String[][] {
+ {"","",}, {"","",}, {"","",},
+ {"","K"}, {"","K"}, {"","K"},
+ {"","M"}, {"","M"}, {"","M"},
+ {"","B"}, {"","B"}, {"","B"},
+ {"","T"}, {"","T"}, {"","T"},
+ });
+ affixes.put("other", new String[][] {
+ {"","",}, {"","",}, {"","",},
+ {"","Ks"}, {"","Ks"}, {"","Ks"},
+ {"","Ms"}, {"","Ms"}, {"","Ms"},
+ {"","Bs"}, {"","Bs"}, {"","Bs"},
+ {"","Ts"}, {"","Ts"}, {"","Ts"},
+ });
+
+ Map<String,String[]> currencyAffixes = new HashMap();
+ currencyAffixes.put("one", new String[] {"", "$"});
+ currencyAffixes.put("other", new String[] {"", "$s"});
+
+ long[] divisors = new long[] {
+ 0,0,0,
+ 1000, 1000, 1000,
+ 1000000, 1000000, 1000000,
+ 1000000000L, 1000000000L, 1000000000L,
+ 1000000000000L, 1000000000000L, 1000000000000L};
+ long[] divisors_err = new long[] {
+ 0,0,0,
+ 13, 13, 13,
+ 1000000, 1000000, 1000000,
+ 1000000000L, 1000000000L, 1000000000L,
+ 1000000000000L, 1000000000000L, 1000000000000L};
+ checkCore(affixes, null, divisors, TestACoreCompactFormatList);
+ checkCore(affixes, currencyAffixes, divisors, TestACoreCompactFormatListCurrency);
+ try {
+ checkCore(affixes, null, divisors_err, TestACoreCompactFormatList);
+ } catch(AssertionError e) {
+ // Exception expected, thus return.
+ return;
+ }
+ fail("Error expected but passed");
+ }
+
+ private void checkCore(Map<String, String[][]> affixes, Map<String, String[]> currencyAffixes, long[] divisors, Object[][] testItems) {
+ Collection<String> debugCreationErrors = new LinkedHashSet();
+ CompactDecimalFormat cdf = new CompactDecimalFormat(
+ "#,###.00",
+ DecimalFormatSymbols.getInstance(new ULocale("fr")),
+ CompactStyle.SHORT, PluralRules.createRules("one: j is 1 or f is 1"),
+ divisors, affixes, currencyAffixes,
+ debugCreationErrors
+ );
+ if (debugCreationErrors.size() != 0) {
+ for (String s : debugCreationErrors) {
+ errln("Creation error: " + s);
+ }
+ } else {
+ checkCdf("special cdf ", cdf, testItems);
+ }
+ }
@Test
public void TestDefaultSignificantDigits() {
- // We are expecting two significant digits for compact formats with one or two zeros,
- // and rounded to the unit for compact formats with three or more zeros.
+ // We are expecting two significant digits as default.
CompactDecimalFormat cdf =
CompactDecimalFormat.getInstance(ULocale.ENGLISH, CompactStyle.SHORT);
- assertEquals("Default significant digits", "120K", cdf.format(123456));
assertEquals("Default significant digits", "12K", cdf.format(12345));
assertEquals("Default significant digits", "1.2K", cdf.format(1234));
assertEquals("Default significant digits", "120", cdf.format(123));
CompactDecimalFormat cdf = CompactDecimalFormat.getInstance(
ULocale.ENGLISH, CompactStyle.LONG);
BigInteger source_int = new BigInteger("31415926535897932384626433");
- cdf.setMaximumFractionDigits(0);
- assertEquals("BigInteger format wrong: ", "31,415,926,535,898 trillion",
+ assertEquals("BigInteger format wrong: ", "31,000,000,000,000 trillion",
cdf.format(source_int));
BigDecimal source_dec = new BigDecimal(source_int);
- assertEquals("BigDecimal format wrong: ", "31,415,926,535,898 trillion",
+ assertEquals("BigDecimal format wrong: ", "31,000,000,000,000 trillion",
cdf.format(source_dec));
}
result = cdf.format(new CurrencyAmount(43000f, Currency.getInstance("USD")));
assertEquals("CDF should correctly format 43000 with currency in 'ar'", "US$ ٤٣ ألف", result);
result = cdf.format(new CurrencyAmount(-43000f, Currency.getInstance("USD")));
- assertEquals("CDF should correctly format -43000 with currency in 'ar'", "-US$ ٤٣ ألف", result);
+ assertEquals("CDF should correctly format -43000 with currency in 'ar'", "US$ -٤٣ ألف", result);
// Extra locale with different positive/negative formats
cdf = CompactDecimalFormat.getInstance(new ULocale("fi"), CompactDecimalFormat.CompactStyle.SHORT);
result = cdf.format(new CurrencyAmount(123000, Currency.getInstance("EUR")));
assertEquals("CDF should correctly format 123000 with currency in 'it'", "120000 €", result);
}
-
- @Test
- public void TestBug11319() {
- if (logKnownIssue("11319", "CDF does not fall back from zh-Hant-HK to zh-Hant")) {
- return;
- }
-
- CompactDecimalFormat cdf = CompactDecimalFormat.getInstance(new ULocale("zh-Hant-HK"), CompactStyle.SHORT);
- String result = cdf.format(958000000L);
- assertEquals("CDF should correctly format 958 million in zh-Hant-HK", "9.6億", result);
- }
-
- @Test
- public void TestBug12975() {
- ULocale locale = new ULocale("it");
- CompactDecimalFormat cdf = CompactDecimalFormat.getInstance(locale, CompactStyle.SHORT);
- String resultCdf = cdf.format(120000);
- DecimalFormat df = (DecimalFormat) DecimalFormat.getInstance(locale);
- String resultDefault = df.format(120000);
- assertEquals("CompactDecimalFormat should use default pattern when compact pattern is unavailable",
- resultDefault, resultCdf);
- }
-
- @Test
- public void TestBug11534() {
- ULocale locale = new ULocale("pt_PT");
- CompactDecimalFormat cdf = CompactDecimalFormat.getInstance(locale, CompactStyle.SHORT);
- String result = cdf.format(1000);
- assertEquals("pt_PT should fall back to pt", "1 mil", result);
- }
-
- @Test
- public void TestBug12181() {
- ULocale loc = ULocale.ENGLISH;
- CompactDecimalFormat cdf = CompactDecimalFormat.getInstance(loc, CompactStyle.SHORT);
- String s = cdf.format(-1500);
- assertEquals("Should work with negative numbers", "-1.5K", s);
- }
}
package com.ibm.icu.dev.test.format;
import java.io.BufferedReader;
-import java.io.ByteArrayOutputStream;
import java.io.IOException;
-import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
* A collection of methods to run the data driven number format test suite.
*/
public class DataDrivenNumberFormatTestUtility {
-
+
/**
* Base class for code under test.
*/
public static abstract class CodeUnderTest {
-
+
/**
* Returns the ID of the code under test. This ID is used to identify
* tests that are known to fail for this particular code under test.
public Character Id() {
return null;
}
-
+
/**
* Runs a single formatting test. On success, returns null.
* On failure, returns the error. This implementation just returns null.
* Subclasses should override.
* @param tuple contains the parameters of the format test.
*/
- public String format(DataDrivenNumberFormatTestData tuple) {
- if (tuple.output != null && tuple.output.equals("fail")) return "fail";
+ public String format(NumberFormatTestData tuple) {
return null;
}
-
+
/**
* Runs a single toPattern test. On success, returns null.
* On failure, returns the error. This implementation just returns null.
* Subclasses should override.
* @param tuple contains the parameters of the format test.
*/
- public String toPattern(DataDrivenNumberFormatTestData tuple) {
- if (tuple.output != null && tuple.output.equals("fail")) return "fail";
+ public String toPattern(NumberFormatTestData tuple) {
return null;
}
-
+
/**
* Runs a single parse test. On success, returns null.
* On failure, returns the error. This implementation just returns null.
* Subclasses should override.
* @param tuple contains the parameters of the format test.
*/
- public String parse(DataDrivenNumberFormatTestData tuple) {
- if (tuple.output != null && tuple.output.equals("fail")) return "fail";
+ public String parse(NumberFormatTestData tuple) {
return null;
}
-
+
/**
* Runs a single parse currency test. On success, returns null.
* On failure, returns the error. This implementation just returns null.
* Subclasses should override.
* @param tuple contains the parameters of the format test.
*/
- public String parseCurrency(DataDrivenNumberFormatTestData tuple) {
- if (tuple.output != null && tuple.output.equals("fail")) return "fail";
+ public String parseCurrency(NumberFormatTestData tuple) {
return null;
}
-
+
/**
* Runs a single select test. On success, returns null.
* On failure, returns the error. This implementation just returns null.
* Subclasses should override.
* @param tuple contains the parameters of the format test.
*/
- public String select(DataDrivenNumberFormatTestData tuple) {
- if (tuple.output != null && tuple.output.equals("fail")) return "fail";
+ public String select(NumberFormatTestData tuple) {
return null;
}
}
-
+
private static enum RunMode {
SKIP_KNOWN_FAILURES,
- INCLUDE_KNOWN_FAILURES
+ INCLUDE_KNOWN_FAILURES
}
-
+
private final CodeUnderTest codeUnderTest;
private String fileLine = null;
private int fileLineNumber = 0;
- private String fileTestName = "";
- private DataDrivenNumberFormatTestData tuple = new DataDrivenNumberFormatTestData();
-
+ private String fileTestName = "";
+ private NumberFormatTestData tuple = new NumberFormatTestData();
+
/**
* Runs all the tests in the data driven test suite against codeUnderTest.
* @param fileName The name of the test file. A relative file name under
* com/ibm/icu/dev/data such as "data.txt"
* @param codeUnderTest the code under test
*/
-
+
static void runSuite(
String fileName, CodeUnderTest codeUnderTest) {
new DataDrivenNumberFormatTestUtility(codeUnderTest)
.run(fileName, RunMode.SKIP_KNOWN_FAILURES);
}
-
+
/**
* Runs every format test in data driven test suite including those
- * that are known to fail. If a test is supposed to fail but actually
- * passes, an error is printed.
- *
+ * that are known to fail.
+ *
* @param fileName The name of the test file. A relative file name under
* com/ibm/icu/dev/data such as "data.txt"
* @param codeUnderTest the code under test
new DataDrivenNumberFormatTestUtility(codeUnderTest)
.run(fileName, RunMode.INCLUDE_KNOWN_FAILURES);
}
-
+
private DataDrivenNumberFormatTestUtility(
CodeUnderTest codeUnderTest) {
this.codeUnderTest = codeUnderTest;
}
-
+
private void run(String fileName, RunMode runMode) {
Character codeUnderTestIdObj = codeUnderTest.Id();
char codeUnderTestId =
if (fileLine != null && fileLine.charAt(0) == '\uFEFF') {
fileLine = fileLine.substring(1);
}
-
+
int state = 0;
List<String> columnValues;
List<String> columnNames = null;
if (state == 0) {
if (fileLine.startsWith("test ")) {
fileTestName = fileLine;
- tuple = new DataDrivenNumberFormatTestData();
+ tuple = new NumberFormatTestData();
} else if (fileLine.startsWith("set ")) {
if (!setTupleField()) {
return;
return;
}
}
- if (runMode == RunMode.INCLUDE_KNOWN_FAILURES || !breaks(codeUnderTestId)) {
- String errorMessage;
- Exception err = null;
- boolean shouldFail = (tuple.output != null && tuple.output.equals("fail"))
- ? !breaks(codeUnderTestId)
- : breaks(codeUnderTestId);
- try {
- errorMessage = isPass(tuple);
- } catch (Exception e) {
- err = e;
- errorMessage = "Exception: " + e + ": " + e.getCause();
- }
- if (shouldFail && errorMessage == null) {
- showError("Expected failure, but passed");
- } else if (!shouldFail && errorMessage != null) {
- if (err != null) {
- ByteArrayOutputStream os = new ByteArrayOutputStream();
- PrintStream ps = new PrintStream(os);
- err.printStackTrace(ps);
- String stackTrace = os.toString();
- showError(errorMessage + " Stack trace: " + stackTrace.substring(0, 500));
- } else {
- showError(errorMessage);
- }
+ if (runMode == RunMode.INCLUDE_KNOWN_FAILURES
+ || !breaks(codeUnderTestId)) {
+ String errorMessage = isPass(tuple);
+ if (errorMessage != null) {
+ showError(errorMessage);
}
}
}
fileLine = null;
}
} catch (Exception e) {
- ByteArrayOutputStream os = new ByteArrayOutputStream();
- PrintStream ps = new PrintStream(os);
- e.printStackTrace(ps);
- String stackTrace = os.toString();
- showError("MAJOR ERROR: " + e.toString() + " Stack trace: " + stackTrace.substring(0,500));
+ showError(e.toString());
+ e.printStackTrace();
} finally {
try {
if (in != null) {
private static boolean isSpace(char c) {
return (c == 0x09 || c == 0x20 || c == 0x3000);
}
-
+
private boolean setTupleField() {
List<String> parts = splitBy(3, (char) 0x20);
if (parts.size() < 3) {
}
return setField(parts.get(1), parts.get(2));
}
-
+
private boolean setField(String name, String value) {
try {
tuple.setField(name, Utility.unescape(value));
return false;
}
}
-
+
private boolean clearField(String name) {
try {
tuple.clearField(name);
return false;
}
}
-
+
private void showError(String message) {
TestFmwk.errln(String.format("line %d: %s\n%s\n%s", fileLineNumber, Utility.escape(message), fileTestName,fileLine));
}
-
+
private List<String> splitBy(char delimiter) {
return splitBy(Integer.MAX_VALUE, delimiter);
}
-
+
private List<String> splitBy(int max, char delimiter) {
- ArrayList<String> result = new ArrayList<String>();
+ ArrayList<String> result = new ArrayList<String>();
int colIdx = 0;
int colStart = 0;
int len = fileLine.length();
}
result.add(fileLine.substring(colStart, len));
return result;
- }
+ }
private boolean readLine(BufferedReader in) throws IOException {
String line = in.readLine();
fileLine = idx == 0 ? "" : line;
return true;
}
-
- private String isPass(DataDrivenNumberFormatTestData tuple) {
+
+ private String isPass(NumberFormatTestData tuple) {
StringBuilder result = new StringBuilder();
if (tuple.format != null && tuple.output != null) {
String errorMessage = codeUnderTest.format(tuple);
* Corporation and others. All Rights Reserved.
**/
-/**
+/**
* Port From: JDK 1.4b1 : java.text.Format.IntlTestDecimalFormatAPI
* Source File: java/text/format/IntlTestDecimalFormatAPI.java
**/
-
+
/*
@test 1.4 98/03/06
@summary test International Decimal Format API
public class IntlTestDecimalFormatAPI extends com.ibm.icu.dev.test.TestFmwk
{
/**
- * Problem 1: simply running
- * decF4.setRoundingMode(java.math.BigDecimal.ROUND_HALF_UP) does not work
+ * Problem 1: simply running
+ * decF4.setRoundingMode(java.math.BigDecimal.ROUND_HALF_UP) does not work
* as decF4.setRoundingIncrement(.0001) must also be run.
- * Problem 2: decF4.format(8.88885) does not return 8.8889 as expected.
- * You must run decF4.format(new BigDecimal(Double.valueOf(8.88885))) in
+ * Problem 2: decF4.format(8.88885) does not return 8.8889 as expected.
+ * You must run decF4.format(new BigDecimal(Double.valueOf(8.88885))) in
* order for this to work as expected.
- * Problem 3: There seems to be no way to set half up to be the default
+ * Problem 3: There seems to be no way to set half up to be the default
* rounding mode.
- * We solved the problem with the code at the bottom of this page however
+ * We solved the problem with the code at the bottom of this page however
* this is not quite general purpose enough to include in icu4j. A static
- * setDefaultRoundingMode function would solve the problem nicely. Also
- * decimal places past 20 are not handled properly. A small ammount of work
+ * setDefaultRoundingMode function would solve the problem nicely. Also
+ * decimal places past 20 are not handled properly. A small ammount of work
* would make bring this up to snuff.
*/
@Test
// problem 2
double number = 8.88885;
String expected = "8.8889";
-
+
String pat = ",##0.0000";
DecimalFormat dec = new DecimalFormat(pat);
dec.setRoundingMode(BigDecimal.ROUND_HALF_UP);
if (!str.equals(expected)) {
errln("Fail: " + number + " x \"" + pat + "\" = \"" +
str + "\", expected \"" + expected + "\"");
- }
+ }
pat = ",##0.0001";
dec = new DecimalFormat(pat);
if (!str.equals(expected)) {
errln("Fail: " + number + " x \"" + pat + "\" = \"" +
str + "\", expected \"" + expected + "\"");
- }
-
+ }
+
// testing 20 decimal places
pat = ",##0.00000000000000000001";
dec = new DecimalFormat(pat);
BigDecimal bignumber = new BigDecimal("8.888888888888888888885");
expected = "8.88888888888888888889";
-
+
dec.setRoundingMode(BigDecimal.ROUND_HALF_UP);
- str = dec.format(bignumber);
+ str = dec.format(bignumber);
if (!str.equals(expected)) {
errln("Fail: " + bignumber + " x \"" + pat + "\" = \"" +
str + "\", expected \"" + expected + "\"");
- }
-
+ }
+
}
- /**
- * This test checks various generic API methods in DecimalFormat to achieve
+ /**
+ * This test checks various generic API methods in DecimalFormat to achieve
* 100% API coverage.
*/
@Test
DecimalFormat decfmt = new DecimalFormat();
MathContext resultICU;
- MathContext comp1 = new MathContext(0, MathContext.PLAIN, false, MathContext.ROUND_HALF_EVEN);
+ MathContext comp1 = new MathContext(0, MathContext.PLAIN);
resultICU = decfmt.getMathContextICU();
if ((comp1.getDigits() != resultICU.getDigits()) ||
(comp1.getForm() != resultICU.getForm()) ||
" / expected: " + comp1.toString());
}
- MathContext comp2 = new MathContext(5, MathContext.ENGINEERING, false, MathContext.ROUND_HALF_EVEN);
+ MathContext comp2 = new MathContext(5, MathContext.ENGINEERING);
decfmt.setMathContextICU(comp2);
resultICU = decfmt.getMathContextICU();
if ((comp2.getDigits() != resultICU.getDigits()) ||
// get default rounding increment
r1 = pat.getRoundingIncrement();
- // set rounding mode with zero increment. Rounding
+ // set rounding mode with zero increment. Rounding
// increment should be set by this operation
pat.setRoundingMode(BigDecimal.ROUND_UP);
r2 = pat.getRoundingIncrement();
}
}
}
-
+
@Test
public void testJB6648()
{
DecimalFormat df = new DecimalFormat();
df.setParseStrict(true);
-
+
String numstr = new String();
-
+
String[] patterns = {
"0",
"00",
"000",
"0,000",
"0.0",
- "#000.0"
+ "#000.0"
};
-
+
for(int i=0; i < patterns.length; i++) {
df.applyPattern(patterns[i]);
- numstr = df.format(5);
+ numstr = df.format(5);
try {
Number n = df.parse(numstr);
logln("INFO: Parsed " + numstr + " -> " + n);
} catch (ParseException pe) {
errln("ERROR: Failed round trip with strict parsing.");
- }
+ }
}
-
+
df.applyPattern(patterns[1]);
- numstr = "005";
+ numstr = "005";
try {
Number n = df.parse(numstr);
logln("INFO: Successful parse for " + numstr + " with strict parse enabled. Number is " + n);
} catch (ParseException pe) {
errln("ERROR: Parse Exception encountered in strict mode: numstr -> " + numstr);
- }
-
+ }
+
}
}
s2 = pat.toPattern();
logln("Extracted pattern is " + s2);
if (!s2.equals(p1)) {
- errln("ERROR: toPattern() result did not match pattern applied: " + p1 + " vs " + s2);
+ errln("ERROR: toPattern() result did not match pattern applied");
}
String p2 = new String("#,##0.0# FF;(#,##0.0# FF)");
String s3;
s3 = pat.toLocalizedPattern();
logln("Extracted pattern is " + s3);
- assertEquals("ERROR: toLocalizedPattern() result did not match pattern applied", p2, s3);
+ if (!s3.equals(p2)) {
+ errln("ERROR: toLocalizedPattern() result did not match pattern applied");
+ }
// ======= Test getStaticClassID()
*******************************************************************************
*/
-/**
+/**
* Port From: ICU4C v1.8.1 : format : IntlTestDecimalFormatSymbols
* Source File: $ICU4CRoot/source/test/intltest/tsdcfmsy.cpp
**/
* Test the API of DecimalFormatSymbols; primarily a simple get/set set.
*/
@Test
- public void TestSymbols() {
- DecimalFormatSymbols fr = new DecimalFormatSymbols(Locale.FRENCH);
+ public void TestSymbols() {
+ DecimalFormatSymbols fr = new DecimalFormatSymbols(Locale.FRENCH);
DecimalFormatSymbols en = new DecimalFormatSymbols(Locale.ENGLISH);
-
+
if (en.equals(fr)) {
errln("ERROR: English DecimalFormatSymbols equal to French");
}
-
+
// just do some VERY basic tests to make sure that get/set work
-
+
char zero = en.getZeroDigit();
fr.setZeroDigit(zero);
if (fr.getZeroDigit() != en.getZeroDigit()) {
errln("ERROR: get/set ZeroDigit failed");
}
-
+
char group = en.getGroupingSeparator();
fr.setGroupingSeparator(group);
if (fr.getGroupingSeparator() != en.getGroupingSeparator()) {
errln("ERROR: get/set GroupingSeparator failed");
}
-
+
char decimal = en.getDecimalSeparator();
fr.setDecimalSeparator(decimal);
if (fr.getDecimalSeparator() != en.getDecimalSeparator()) {
errln("ERROR: get/set DecimalSeparator failed");
}
-
+
char perMill = en.getPerMill();
fr.setPerMill(perMill);
if (fr.getPerMill() != en.getPerMill()) {
errln("ERROR: get/set PerMill failed");
}
-
+
char percent = en.getPercent();
fr.setPercent(percent);
if (fr.getPercent() != en.getPercent()) {
errln("ERROR: get/set Percent failed");
}
-
+
char digit = en.getDigit();
fr.setDigit(digit);
if (fr.getPercent() != en.getPercent()) {
errln("ERROR: get/set Percent failed");
}
-
+
char patternSeparator = en.getPatternSeparator();
fr.setPatternSeparator(patternSeparator);
if (fr.getPatternSeparator() != en.getPatternSeparator()) {
errln("ERROR: get/set PatternSeparator failed");
}
-
+
String infinity = en.getInfinity();
fr.setInfinity(infinity);
String infinity2 = fr.getInfinity();
if (!infinity.equals(infinity2)) {
errln("ERROR: get/set Infinity failed");
}
-
+
String nan = en.getNaN();
fr.setNaN(nan);
String nan2 = fr.getNaN();
if (!nan.equals(nan2)) {
errln("ERROR: get/set NaN failed");
}
-
+
char minusSign = en.getMinusSign();
fr.setMinusSign(minusSign);
if (fr.getMinusSign() != en.getMinusSign()) {
errln("ERROR: get/set MinusSign failed");
}
-
+
// char exponential = en.getExponentialSymbol();
// fr.setExponentialSymbol(exponential);
// if(fr.getExponentialSymbol() != en.getExponentialSymbol()) {
// errln("ERROR: get/set Exponential failed");
// }
-
+
//DecimalFormatSymbols foo = new DecimalFormatSymbols(); //The variable is never used
-
+
en = (DecimalFormatSymbols) fr.clone();
-
+
if (!en.equals(fr)) {
errln("ERROR: Clone failed");
}
-
+
DecimalFormatSymbols sym = new DecimalFormatSymbols(Locale.US);
-
+
verify(34.5, "00.00", sym, "34.50");
sym.setDecimalSeparator('S');
verify(34.5, "00.00", sym, "34S50");
sym.setPercent('P');
verify(34.5, "00 %", sym, "3450 P");
sym.setCurrencySymbol("D");
- verify(34.5, "\u00a4##.##", sym, "D34.50");
+ verify(34.5, "\u00a4##.##", sym, "D34.5");
sym.setGroupingSeparator('|');
verify(3456.5, "0,000.##", sym, "3|456S5");
}
-
+
/** helper functions**/
public void verify(double value, String pattern, DecimalFormatSymbols sym, String expected) {
DecimalFormat df = new DecimalFormat(pattern, sym);
FieldPosition pos = new FieldPosition(-1);
buffer = df.format(value, buffer, pos);
if(!buffer.toString().equals(expected)){
- errln("ERROR: format failed after setSymbols()\n Expected" +
+ errln("ERROR: format failed after setSymbols()\n Expected" +
expected + ", Got " + buffer);
}
}
import org.junit.Test;
import com.ibm.icu.dev.test.TestFmwk;
-import com.ibm.icu.dev.test.serializable.FormatHandler;
import com.ibm.icu.dev.test.serializable.SerializableTestUtility;
import com.ibm.icu.impl.Pair;
import com.ibm.icu.impl.Utility;
}
}
- @Test
- public void testBug11966() {
- Locale locale = new Locale("en", "AU");
- MeasureFormat.getInstance(locale, MeasureFormat.FormatWidth.WIDE);
- // Should not throw an exception.
- }
-
// DO NOT DELETE THIS FUNCTION! It may appear as dead code, but we use this to generate code
// for MeasureFormat during the release process.
static Map<MeasureUnit, Pair<MeasureUnit, MeasureUnit>> getUnitsToPerParts() {
public static class MeasureFormatHandler implements SerializableTestUtility.Handler
{
- FormatHandler.NumberFormatHandler nfh = new FormatHandler.NumberFormatHandler();
-
@Override
public Object[] getTestObjects()
{
MeasureFormat b1 = (MeasureFormat) b;
return a1.getLocale().equals(b1.getLocale())
&& a1.getWidth().equals(b1.getWidth())
- && nfh.hasSameBehavior(a1.getNumberFormat(), b1.getNumberFormat());
+ && a1.getNumberFormat().equals(b1.getNumberFormat())
+ ;
}
}
}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.dev.test.format;
-
-import java.math.BigDecimal;
-import java.text.ParsePosition;
-
-import org.junit.Test;
-
-import com.ibm.icu.text.DecimalFormatSymbols;
-import com.ibm.icu.text.DecimalFormat_ICU58;
-import com.ibm.icu.util.CurrencyAmount;
-import com.ibm.icu.util.ULocale;
-
-public class NumberFormatDataDrivenTest {
-
- private static ULocale EN = new ULocale("en");
-
- private static Number toNumber(String s) {
- if (s.equals("NaN")) {
- return Double.NaN;
- } else if (s.equals("-Inf")) {
- return Double.NEGATIVE_INFINITY;
- } else if (s.equals("Inf")) {
- return Double.POSITIVE_INFINITY;
- }
- return new BigDecimal(s);
- }
-
- private DataDrivenNumberFormatTestUtility.CodeUnderTest ICU58 =
- new DataDrivenNumberFormatTestUtility.CodeUnderTest() {
- @Override
- public Character Id() {
- return 'J';
- }
-
- @Override
- public String format(DataDrivenNumberFormatTestData tuple) {
- DecimalFormat_ICU58 fmt = createDecimalFormat(tuple);
- String actual = fmt.format(toNumber(tuple.format));
- String expected = tuple.output;
- if (!expected.equals(actual)) {
- return "Expected " + expected + ", got " + actual;
- }
- return null;
- }
-
- @Override
- public String toPattern(DataDrivenNumberFormatTestData tuple) {
- DecimalFormat_ICU58 fmt = createDecimalFormat(tuple);
- StringBuilder result = new StringBuilder();
- if (tuple.toPattern != null) {
- String expected = tuple.toPattern;
- String actual = fmt.toPattern();
- if (!expected.equals(actual)) {
- result.append("Expected toPattern=" + expected + ", got " + actual);
- }
- }
- if (tuple.toLocalizedPattern != null) {
- String expected = tuple.toLocalizedPattern;
- String actual = fmt.toLocalizedPattern();
- if (!expected.equals(actual)) {
- result.append("Expected toLocalizedPattern=" + expected + ", got " + actual);
- }
- }
- return result.length() == 0 ? null : result.toString();
- }
-
- @Override
- public String parse(DataDrivenNumberFormatTestData tuple) {
- DecimalFormat_ICU58 fmt = createDecimalFormat(tuple);
- ParsePosition ppos = new ParsePosition(0);
- Number actual = fmt.parse(tuple.parse, ppos);
- if (ppos.getIndex() == 0) {
- return "Parse failed; got " + actual + ", but expected " + tuple.output;
- }
- if (tuple.output.equals("fail")) {
- return null;
- }
- Number expected = toNumber(tuple.output);
- // number types cannot be compared, this is the best we can do.
- if (expected.doubleValue() != actual.doubleValue()
- && !Double.isNaN(expected.doubleValue())
- && !Double.isNaN(expected.doubleValue())) {
- return "Expected: " + expected + ", got: " + actual;
- }
- return null;
- }
-
- @Override
- public String parseCurrency(DataDrivenNumberFormatTestData tuple) {
- DecimalFormat_ICU58 fmt = createDecimalFormat(tuple);
- ParsePosition ppos = new ParsePosition(0);
- CurrencyAmount currAmt = fmt.parseCurrency(tuple.parse, ppos);
- if (ppos.getIndex() == 0) {
- return "Parse failed; got " + currAmt + ", but expected " + tuple.output;
- }
- if (tuple.output.equals("fail")) {
- return null;
- }
- Number expected = toNumber(tuple.output);
- Number actual = currAmt.getNumber();
- // number types cannot be compared, this is the best we can do.
- if (expected.doubleValue() != actual.doubleValue()
- && !Double.isNaN(expected.doubleValue())
- && !Double.isNaN(expected.doubleValue())) {
- return "Expected: " + expected + ", got: " + actual;
- }
-
- if (!tuple.outputCurrency.equals(currAmt.getCurrency().toString())) {
- return "Expected currency: " + tuple.outputCurrency + ", got: " + currAmt.getCurrency();
- }
- return null;
- }
-
- /**
- * @param tuple
- * @return
- */
- private DecimalFormat_ICU58 createDecimalFormat(DataDrivenNumberFormatTestData tuple) {
-
- DecimalFormat_ICU58 fmt =
- new DecimalFormat_ICU58(
- tuple.pattern == null ? "0" : tuple.pattern,
- new DecimalFormatSymbols(tuple.locale == null ? EN : tuple.locale));
- adjustDecimalFormat(tuple, fmt);
- return fmt;
- }
- /**
- * @param tuple
- * @param fmt
- */
- private void adjustDecimalFormat(
- DataDrivenNumberFormatTestData tuple, DecimalFormat_ICU58 fmt) {
- if (tuple.minIntegerDigits != null) {
- fmt.setMinimumIntegerDigits(tuple.minIntegerDigits);
- }
- if (tuple.maxIntegerDigits != null) {
- fmt.setMaximumIntegerDigits(tuple.maxIntegerDigits);
- }
- if (tuple.minFractionDigits != null) {
- fmt.setMinimumFractionDigits(tuple.minFractionDigits);
- }
- if (tuple.maxFractionDigits != null) {
- fmt.setMaximumFractionDigits(tuple.maxFractionDigits);
- }
- if (tuple.currency != null) {
- fmt.setCurrency(tuple.currency);
- }
- if (tuple.minGroupingDigits != null) {
- // Oops we don't support this.
- }
- if (tuple.useSigDigits != null) {
- fmt.setSignificantDigitsUsed(tuple.useSigDigits != 0);
- }
- if (tuple.minSigDigits != null) {
- fmt.setMinimumSignificantDigits(tuple.minSigDigits);
- }
- if (tuple.maxSigDigits != null) {
- fmt.setMaximumSignificantDigits(tuple.maxSigDigits);
- }
- if (tuple.useGrouping != null) {
- fmt.setGroupingUsed(tuple.useGrouping != 0);
- }
- if (tuple.multiplier != null) {
- fmt.setMultiplier(tuple.multiplier);
- }
- if (tuple.roundingIncrement != null) {
- fmt.setRoundingIncrement(tuple.roundingIncrement.doubleValue());
- }
- if (tuple.formatWidth != null) {
- fmt.setFormatWidth(tuple.formatWidth);
- }
- if (tuple.padCharacter != null && tuple.padCharacter.length() > 0) {
- fmt.setPadCharacter(tuple.padCharacter.charAt(0));
- }
- if (tuple.useScientific != null) {
- fmt.setScientificNotation(tuple.useScientific != 0);
- }
- if (tuple.grouping != null) {
- fmt.setGroupingSize(tuple.grouping);
- }
- if (tuple.grouping2 != null) {
- fmt.setSecondaryGroupingSize(tuple.grouping2);
- }
- if (tuple.roundingMode != null) {
- fmt.setRoundingMode(tuple.roundingMode);
- }
- if (tuple.currencyUsage != null) {
- fmt.setCurrencyUsage(tuple.currencyUsage);
- }
- if (tuple.minimumExponentDigits != null) {
- fmt.setMinimumExponentDigits(tuple.minimumExponentDigits.byteValue());
- }
- if (tuple.exponentSignAlwaysShown != null) {
- fmt.setExponentSignAlwaysShown(tuple.exponentSignAlwaysShown != 0);
- }
- if (tuple.decimalSeparatorAlwaysShown != null) {
- fmt.setDecimalSeparatorAlwaysShown(tuple.decimalSeparatorAlwaysShown != 0);
- }
- if (tuple.padPosition != null) {
- fmt.setPadPosition(tuple.padPosition);
- }
- if (tuple.positivePrefix != null) {
- fmt.setPositivePrefix(tuple.positivePrefix);
- }
- if (tuple.positiveSuffix != null) {
- fmt.setPositiveSuffix(tuple.positiveSuffix);
- }
- if (tuple.negativePrefix != null) {
- fmt.setNegativePrefix(tuple.negativePrefix);
- }
- if (tuple.negativeSuffix != null) {
- fmt.setNegativeSuffix(tuple.negativeSuffix);
- }
- if (tuple.localizedPattern != null) {
- fmt.applyLocalizedPattern(tuple.localizedPattern);
- }
- int lenient = tuple.lenient == null ? 1 : tuple.lenient.intValue();
- fmt.setParseStrict(lenient == 0);
- if (tuple.parseIntegerOnly != null) {
- fmt.setParseIntegerOnly(tuple.parseIntegerOnly != 0);
- }
- if (tuple.parseCaseSensitive != null) {
- // Not supported.
- }
- if (tuple.decimalPatternMatchRequired != null) {
- fmt.setDecimalPatternMatchRequired(tuple.decimalPatternMatchRequired != 0);
- }
- if (tuple.parseNoExponent != null) {
- // Oops, not supported for now
- }
- }
- };
-
- private DataDrivenNumberFormatTestUtility.CodeUnderTest JDK =
- new DataDrivenNumberFormatTestUtility.CodeUnderTest() {
- @Override
- public Character Id() {
- return 'K';
- }
-
- @Override
- public String format(DataDrivenNumberFormatTestData tuple) {
- java.text.DecimalFormat fmt = createDecimalFormat(tuple);
- String actual = fmt.format(toNumber(tuple.format));
- String expected = tuple.output;
- if (!expected.equals(actual)) {
- return "Expected " + expected + ", got " + actual;
- }
- return null;
- }
-
- @Override
- public String toPattern(DataDrivenNumberFormatTestData tuple) {
- java.text.DecimalFormat fmt = createDecimalFormat(tuple);
- StringBuilder result = new StringBuilder();
- if (tuple.toPattern != null) {
- String expected = tuple.toPattern;
- String actual = fmt.toPattern();
- if (!expected.equals(actual)) {
- result.append("Expected toPattern=" + expected + ", got " + actual);
- }
- }
- if (tuple.toLocalizedPattern != null) {
- String expected = tuple.toLocalizedPattern;
- String actual = fmt.toLocalizedPattern();
- if (!expected.equals(actual)) {
- result.append("Expected toLocalizedPattern=" + expected + ", got " + actual);
- }
- }
- return result.length() == 0 ? null : result.toString();
- }
-
- @Override
- public String parse(DataDrivenNumberFormatTestData tuple) {
- java.text.DecimalFormat fmt = createDecimalFormat(tuple);
- ParsePosition ppos = new ParsePosition(0);
- Number actual = fmt.parse(tuple.parse, ppos);
- if (ppos.getIndex() == 0) {
- return "Parse failed; got " + actual + ", but expected " + tuple.output;
- }
- if (tuple.output.equals("fail")) {
- return null;
- }
- Number expected = toNumber(tuple.output);
- // number types cannot be compared, this is the best we can do.
- if (expected.doubleValue() != actual.doubleValue()
- && !Double.isNaN(expected.doubleValue())
- && !Double.isNaN(expected.doubleValue())) {
- return "Expected: " + expected + ", got: " + actual;
- }
- return null;
- }
-
- /**
- * @param tuple
- * @return
- */
- private java.text.DecimalFormat createDecimalFormat(DataDrivenNumberFormatTestData tuple) {
- java.text.DecimalFormat fmt =
- new java.text.DecimalFormat(
- tuple.pattern == null ? "0" : tuple.pattern,
- new java.text.DecimalFormatSymbols(
- (tuple.locale == null ? EN : tuple.locale).toLocale()));
- adjustDecimalFormat(tuple, fmt);
- return fmt;
- }
-
- /**
- * @param tuple
- * @param fmt
- */
- private void adjustDecimalFormat(
- DataDrivenNumberFormatTestData tuple, java.text.DecimalFormat fmt) {
- if (tuple.minIntegerDigits != null) {
- fmt.setMinimumIntegerDigits(tuple.minIntegerDigits);
- }
- if (tuple.maxIntegerDigits != null) {
- fmt.setMaximumIntegerDigits(tuple.maxIntegerDigits);
- }
- if (tuple.minFractionDigits != null) {
- fmt.setMinimumFractionDigits(tuple.minFractionDigits);
- }
- if (tuple.maxFractionDigits != null) {
- fmt.setMaximumFractionDigits(tuple.maxFractionDigits);
- }
- if (tuple.currency != null) {
- fmt.setCurrency(java.util.Currency.getInstance(tuple.currency.toString()));
- }
- if (tuple.minGroupingDigits != null) {
- // Oops we don't support this.
- }
- if (tuple.useSigDigits != null) {
- // Oops we don't support this
- }
- if (tuple.minSigDigits != null) {
- // Oops we don't support this
- }
- if (tuple.maxSigDigits != null) {
- // Oops we don't support this
- }
- if (tuple.useGrouping != null) {
- fmt.setGroupingUsed(tuple.useGrouping != 0);
- }
- if (tuple.multiplier != null) {
- fmt.setMultiplier(tuple.multiplier);
- }
- if (tuple.roundingIncrement != null) {
- // Not supported
- }
- if (tuple.formatWidth != null) {
- // Not supported
- }
- if (tuple.padCharacter != null && tuple.padCharacter.length() > 0) {
- // Not supported
- }
- if (tuple.useScientific != null) {
- // Not supported
- }
- if (tuple.grouping != null) {
- fmt.setGroupingSize(tuple.grouping);
- }
- if (tuple.grouping2 != null) {
- // Not supported
- }
- if (tuple.roundingMode != null) {
- // Not supported
- }
- if (tuple.currencyUsage != null) {
- // Not supported
- }
- if (tuple.minimumExponentDigits != null) {
- // Not supported
- }
- if (tuple.exponentSignAlwaysShown != null) {
- // Not supported
- }
- if (tuple.decimalSeparatorAlwaysShown != null) {
- fmt.setDecimalSeparatorAlwaysShown(tuple.decimalSeparatorAlwaysShown != 0);
- }
- if (tuple.padPosition != null) {
- // Not supported
- }
- if (tuple.positivePrefix != null) {
- fmt.setPositivePrefix(tuple.positivePrefix);
- }
- if (tuple.positiveSuffix != null) {
- fmt.setPositiveSuffix(tuple.positiveSuffix);
- }
- if (tuple.negativePrefix != null) {
- fmt.setNegativePrefix(tuple.negativePrefix);
- }
- if (tuple.negativeSuffix != null) {
- fmt.setNegativeSuffix(tuple.negativeSuffix);
- }
- if (tuple.localizedPattern != null) {
- fmt.applyLocalizedPattern(tuple.localizedPattern);
- }
-
- // lenient parsing not supported by JDK
- if (tuple.parseIntegerOnly != null) {
- fmt.setParseIntegerOnly(tuple.parseIntegerOnly != 0);
- }
- if (tuple.parseCaseSensitive != null) {
- // Not supported.
- }
- if (tuple.decimalPatternMatchRequired != null) {
- // Oops, not supported
- }
- if (tuple.parseNoExponent != null) {
- // Oops, not supported for now
- }
- }
- };
-
- @Test
- public void TestDataDrivenICU58() {
- DataDrivenNumberFormatTestUtility.runFormatSuiteIncludingKnownFailures(
- "numberformattestspecification.txt", ICU58);
- }
-
- @Test
- public void TestDataDrivenJDK() {
- DataDrivenNumberFormatTestUtility.runFormatSuiteIncludingKnownFailures(
- "numberformattestspecification.txt", JDK);
- }
-
- @Test
- public void TestDataDrivenShane() {
- ShanesDataDrivenTester.run();
- }
-}
*******************************************************************************
*/
-/**
+/**
* Port From: ICU4C v1.8.1 : format : NumberFormatRegressionTest
* Source File: $ICU4CRoot/source/test/intltest/numrgts.cpp
**/
import com.ibm.icu.util.Calendar;
import com.ibm.icu.util.ULocale;
-/**
+/**
* Performs regression test for MessageFormat
**/
public class NumberFormatRegressionTest extends com.ibm.icu.dev.test.TestFmwk {
errln("FAIL");
}
}
-
+
/**
* DateFormat should call setIntegerParseOnly(TRUE) on adopted
* NumberFormat objects.
*/
@Test
public void TestJ691() {
-
+
Locale loc = new Locale("fr", "CH");
-
+
// set up the input date string & expected output
String udt = "11.10.2000";
String exp = "11.10.00";
-
+
// create a Calendar for this locale
Calendar cal = Calendar.getInstance(loc);
-
+
// create a NumberFormat for this locale
NumberFormat nf = NumberFormat.getInstance(loc);
-
+
// *** Here's the key: We don't want to have to do THIS:
//nf.setParseIntegerOnly(true);
// However with changes to fr_CH per cldrbug:9370 we have to do the following:
// create the DateFormat
DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, loc);
-
+
df.setCalendar(cal);
df.setNumberFormat(nf);
-
+
// set parsing to lenient & parse
Date ulocdat = new Date();
df.setLenient(true);
}
// format back to a string
String outString = df.format(ulocdat);
-
+
if (!outString.equals(exp)) {
errln("FAIL: " + udt + " => " + outString);
}
}
-
+
/**
* Test getIntegerInstance();
*/
@Test
public void Test4408066() {
-
+
NumberFormat nf1 = NumberFormat.getIntegerInstance();
NumberFormat nf2 = NumberFormat.getIntegerInstance(Locale.CHINA);
-
+
//test isParseIntegerOnly
if (!nf1.isParseIntegerOnly() || !nf2.isParseIntegerOnly()) {
errln("Failed : Integer Number Format Instance should set setParseIntegerOnly(true)");
}
-
+
//Test format
{
double[] data = {
- -3.75, -2.5, -1.5,
- -1.25, 0, 1.0,
- 1.25, 1.5, 2.5,
+ -3.75, -2.5, -1.5,
+ -1.25, 0, 1.0,
+ 1.25, 1.5, 2.5,
3.75, 10.0, 255.5
};
String[] expected = {
"1", "2", "2",
"4", "10", "256"
};
-
+
for (int i = 0; i < data.length; ++i) {
String result = nf1.format(data[i]);
if (!result.equals(expected[i])) {
- errln("Failed => Source: " + Double.toString(data[i])
+ errln("Failed => Source: " + Double.toString(data[i])
+ ";Formatted : " + result
+ ";but expectted: " + expected[i]);
}
//Test parse, Parsing should stop at "."
{
String data[] = {
- "-3.75", "-2.5", "-1.5",
- "-1.25", "0", "1.0",
- "1.25", "1.5", "2.5",
+ "-3.75", "-2.5", "-1.5",
+ "-1.25", "0", "1.0",
+ "1.25", "1.5", "2.5",
"3.75", "10.0", "255.5"
};
long[] expected = {
1, 1, 2,
3, 10, 255
};
-
+
for (int i = 0; i < data.length; ++i) {
Number n = null;
try {
errln("Failed: Integer Number Format should parse string to Long/Integer");
}
if (n.longValue() != expected[i]) {
- errln("Failed=> Source: " + data[i]
+ errln("Failed=> Source: " + data[i]
+ ";result : " + n.toString()
+ ";expected :" + Long.toString(expected[i]));
}
}
}
}
-
+
//Test New serialized DecimalFormat(2.0) read old serialized forms of DecimalFormat(1.3.1.1)
@Test
public void TestSerialization() throws IOException{
byte[][] contents = NumberFormatSerialTestData.getContent();
double data = 1234.56;
String[] expected = {
- "1,234.56", "$1,234.56", "1.23456E3", "1,234.56"};
+ "1,234.56", "$1,234.56", "123,456%", "1.23456E3"};
for (int i = 0; i < 4; ++i) {
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(contents[i]));
try {
NumberFormat format = (NumberFormat) ois.readObject();
String result = format.format(data);
- assertEquals("Deserialization new version should read old version", expected[i], result);
+ if (result.equals(expected[i])) {
+ logln("OK: Deserialized bogus NumberFormat(new version read old version)");
+ } else {
+ errln("FAIL: the test data formats are not euqal");
+ }
} catch (Exception e) {
warnln("FAIL: " + e.getMessage());
}
try {
Number n = nfmt.parse(data[i]);
if (expected[i] != n.doubleValue()) {
- errln("Failed: Parsed result for " + data[i] + ": "
+ errln("Failed: Parsed result for " + data[i] + ": "
+ n.doubleValue() + " / expected: " + expected[i]);
}
} catch (ParseException pe) {
@Test
public void TestSurrogatesParsing() { // Test parsing of numbers that use digits from the supplemental planes.
final String[] data = {
- "1\ud801\udca2,3\ud801\udca45.67", //
- "\ud801\udca1\ud801\udca2,\ud801\udca3\ud801\udca4\ud801\udca5.\ud801\udca6\ud801\udca7\ud801\udca8", //
+ "1\ud801\udca2,3\ud801\udca45.67", //
+ "\ud801\udca1\ud801\udca2,\ud801\udca3\ud801\udca4\ud801\udca5.\ud801\udca6\ud801\udca7\ud801\udca8", //
"\ud835\udfd2.\ud835\udfd7E-\ud835\udfd1",
"\ud835\udfd3.8E-0\ud835\udfd0"
};
try {
Number n = nfmt.parse(data[i]);
if (expected[i] != n.doubleValue()) {
- errln("Failed: Parsed result for " + data[i] + ": "
+ errln("Failed: Parsed result for " + data[i] + ": "
+ n.doubleValue() + " / expected: " + expected[i]);
}
} catch (ParseException pe) {
void checkNBSPPatternRtNum(String testcase, NumberFormat nf, double myNumber) {
String myString = nf.format(myNumber);
-
+
double aNumber;
try {
aNumber = nf.parse(myString).doubleValue();
public void TestNBSPInPattern() {
NumberFormat nf = null;
String testcase;
-
-
+
+
testcase="ar_AE UNUM_CURRENCY";
nf = NumberFormat.getCurrencyInstance(new ULocale("ar_AE"));
checkNBSPPatternRT(testcase, nf);
- // if we don't have CLDR 1.6 data, bring out the problem anyways
-
+ // if we don't have CLDR 1.6 data, bring out the problem anyways
+
String SPECIAL_PATTERN = "\u00A4\u00A4'\u062f.\u0625.\u200f\u00a0'###0.00";
testcase = "ar_AE special pattern: " + SPECIAL_PATTERN;
nf = new DecimalFormat();
((DecimalFormat)nf).applyPattern(SPECIAL_PATTERN);
checkNBSPPatternRT(testcase, nf);
-
+
}
/*
errln("FAIL: Parsed result: " + num + " - expected: " + val);
}
}
-
- @Test
- public void TestAffixesNoCurrency() {
- ULocale locale = new ULocale("en");
- DecimalFormat nf = (DecimalFormat) NumberFormat.getInstance(locale, NumberFormat.PLURALCURRENCYSTYLE);
- assertEquals(
- "Positive suffix should contain the single currency sign when no currency is set",
- " \u00A4",
- nf.getPositiveSuffix());
- }
}
public class NumberFormatSerialTestData {
//get Content
public static byte[][] getContent() {
- return content;
+ return content;
}
//NumberFormat.getInstance(Locale.US)
};
+ //NumberFormat.getPercentInstance(Locale.US)
+ static byte[] percentInstance = new byte[]{
+ -84, -19, 0, 5, 115, 114, 0, 30, 99, 111, 109, 46, 105, 98, 109, 46, 105, 99, 117, 46,
+ 116, 101, 120, 116, 46, 68, 101, 99, 105, 109, 97, 108, 70, 111, 114, 109, 97, 116, 11, -1,
+ 3, 98, -40, 114, 48, 58, 2, 0, 22, 90, 0, 27, 100, 101, 99, 105, 109, 97, 108, 83,
+ 101, 112, 97, 114, 97, 116, 111, 114, 65, 108, 119, 97, 121, 115, 83, 104, 111, 119, 110, 90,
+ 0, 23, 101, 120, 112, 111, 110, 101, 110, 116, 83, 105, 103, 110, 65, 108, 119, 97, 121, 115,
+ 83, 104, 111, 119, 110, 73, 0, 11, 102, 111, 114, 109, 97, 116, 87, 105, 100, 116, 104, 66,
+ 0, 12, 103, 114, 111, 117, 112, 105, 110, 103, 83, 105, 122, 101, 66, 0, 13, 103, 114, 111,
+ 117, 112, 105, 110, 103, 83, 105, 122, 101, 50, 66, 0, 17, 109, 105, 110, 69, 120, 112, 111,
+ 110, 101, 110, 116, 68, 105, 103, 105, 116, 115, 73, 0, 10, 109, 117, 108, 116, 105, 112, 108,
+ 105, 101, 114, 67, 0, 3, 112, 97, 100, 73, 0, 11, 112, 97, 100, 80, 111, 115, 105, 116,
+ 105, 111, 110, 73, 0, 12, 114, 111, 117, 110, 100, 105, 110, 103, 77, 111, 100, 101, 73, 0,
+ 21, 115, 101, 114, 105, 97, 108, 86, 101, 114, 115, 105, 111, 110, 79, 110, 83, 116, 114, 101,
+ 97, 109, 90, 0, 22, 117, 115, 101, 69, 120, 112, 111, 110, 101, 110, 116, 105, 97, 108, 78,
+ 111, 116, 97, 116, 105, 111, 110, 76, 0, 16, 110, 101, 103, 80, 114, 101, 102, 105, 120, 80,
+ 97, 116, 116, 101, 114, 110, 116, 0, 18, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47,
+ 83, 116, 114, 105, 110, 103, 59, 76, 0, 16, 110, 101, 103, 83, 117, 102, 102, 105, 120, 80,
+ 97, 116, 116, 101, 114, 110, 113, 0, 126, 0, 1, 76, 0, 14, 110, 101, 103, 97, 116, 105,
+ 118, 101, 80, 114, 101, 102, 105, 120, 113, 0, 126, 0, 1, 76, 0, 14, 110, 101, 103, 97,
+ 116, 105, 118, 101, 83, 117, 102, 102, 105, 120, 113, 0, 126, 0, 1, 76, 0, 16, 112, 111,
+ 115, 80, 114, 101, 102, 105, 120, 80, 97, 116, 116, 101, 114, 110, 113, 0, 126, 0, 1, 76,
+ 0, 16, 112, 111, 115, 83, 117, 102, 102, 105, 120, 80, 97, 116, 116, 101, 114, 110, 113, 0,
+ 126, 0, 1, 76, 0, 14, 112, 111, 115, 105, 116, 105, 118, 101, 80, 114, 101, 102, 105, 120,
+ 113, 0, 126, 0, 1, 76, 0, 14, 112, 111, 115, 105, 116, 105, 118, 101, 83, 117, 102, 102,
+ 105, 120, 113, 0, 126, 0, 1, 76, 0, 17, 114, 111, 117, 110, 100, 105, 110, 103, 73, 110,
+ 99, 114, 101, 109, 101, 110, 116, 116, 0, 22, 76, 106, 97, 118, 97, 47, 109, 97, 116, 104,
+ 47, 66, 105, 103, 68, 101, 99, 105, 109, 97, 108, 59, 76, 0, 7, 115, 121, 109, 98, 111,
+ 108, 115, 116, 0, 39, 76, 99, 111, 109, 47, 105, 98, 109, 47, 105, 99, 117, 47, 116, 101,
+ 120, 116, 47, 68, 101, 99, 105, 109, 97, 108, 70, 111, 114, 109, 97, 116, 83, 121, 109, 98,
+ 111, 108, 115, 59, 120, 114, 0, 29, 99, 111, 109, 46, 105, 98, 109, 46, 105, 99, 117, 46,
+ 116, 101, 120, 116, 46, 78, 117, 109, 98, 101, 114, 70, 111, 114, 109, 97, 116, -33, -10, -77,
+ -65, 19, 125, 7, -24, 3, 0, 11, 90, 0, 12, 103, 114, 111, 117, 112, 105, 110, 103, 85,
+ 115, 101, 100, 66, 0, 17, 109, 97, 120, 70, 114, 97, 99, 116, 105, 111, 110, 68, 105, 103,
+ 105, 116, 115, 66, 0, 16, 109, 97, 120, 73, 110, 116, 101, 103, 101, 114, 68, 105, 103, 105,
+ 116, 115, 73, 0, 21, 109, 97, 120, 105, 109, 117, 109, 70, 114, 97, 99, 116, 105, 111, 110,
+ 68, 105, 103, 105, 116, 115, 73, 0, 20, 109, 97, 120, 105, 109, 117, 109, 73, 110, 116, 101,
+ 103, 101, 114, 68, 105, 103, 105, 116, 115, 66, 0, 17, 109, 105, 110, 70, 114, 97, 99, 116,
+ 105, 111, 110, 68, 105, 103, 105, 116, 115, 66, 0, 16, 109, 105, 110, 73, 110, 116, 101, 103,
+ 101, 114, 68, 105, 103, 105, 116, 115, 73, 0, 21, 109, 105, 110, 105, 109, 117, 109, 70, 114,
+ 97, 99, 116, 105, 111, 110, 68, 105, 103, 105, 116, 115, 73, 0, 20, 109, 105, 110, 105, 109,
+ 117, 109, 73, 110, 116, 101, 103, 101, 114, 68, 105, 103, 105, 116, 115, 90, 0, 16, 112, 97,
+ 114, 115, 101, 73, 110, 116, 101, 103, 101, 114, 79, 110, 108, 121, 73, 0, 21, 115, 101, 114,
+ 105, 97, 108, 86, 101, 114, 115, 105, 111, 110, 79, 110, 83, 116, 114, 101, 97, 109, 120, 114,
+ 0, 16, 106, 97, 118, 97, 46, 116, 101, 120, 116, 46, 70, 111, 114, 109, 97, 116, -5, -40,
+ -68, 18, -23, 15, 24, 67, 2, 0, 0, 120, 112, 1, 0, 127, 0, 0, 0, 0, 0, 0,
+ 1, 53, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 120, 0, 0,
+ 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 100, 0, 32, 0, 0, 0, 0, 0, 0, 0,
+ 6, 0, 0, 0, 2, 0, 116, 0, 1, 45, 116, 0, 1, 37, 116, 0, 1, 45, 116, 0,
+ 1, 37, 116, 0, 0, 113, 0, 126, 0, 8, 116, 0, 0, 116, 0, 1, 37, 112, 115, 114,
+ 0, 37, 99, 111, 109, 46, 105, 98, 109, 46, 105, 99, 117, 46, 116, 101, 120, 116, 46, 68,
+ 101, 99, 105, 109, 97, 108, 70, 111, 114, 109, 97, 116, 83, 121, 109, 98, 111, 108, 115, 80,
+ 29, 23, -103, 8, 104, -109, -100, 2, 0, 18, 67, 0, 16, 100, 101, 99, 105, 109, 97, 108,
+ 83, 101, 112, 97, 114, 97, 116, 111, 114, 67, 0, 5, 100, 105, 103, 105, 116, 67, 0, 11,
+ 101, 120, 112, 111, 110, 101, 110, 116, 105, 97, 108, 67, 0, 17, 103, 114, 111, 117, 112, 105,
+ 110, 103, 83, 101, 112, 97, 114, 97, 116, 111, 114, 67, 0, 9, 109, 105, 110, 117, 115, 83,
+ 105, 103, 110, 67, 0, 17, 109, 111, 110, 101, 116, 97, 114, 121, 83, 101, 112, 97, 114, 97,
+ 116, 111, 114, 67, 0, 9, 112, 97, 100, 69, 115, 99, 97, 112, 101, 67, 0, 16, 112, 97,
+ 116, 116, 101, 114, 110, 83, 101, 112, 97, 114, 97, 116, 111, 114, 67, 0, 7, 112, 101, 114,
+ 77, 105, 108, 108, 67, 0, 7, 112, 101, 114, 99, 101, 110, 116, 67, 0, 8, 112, 108, 117,
+ 115, 83, 105, 103, 110, 73, 0, 21, 115, 101, 114, 105, 97, 108, 86, 101, 114, 115, 105, 111,
+ 110, 79, 110, 83, 116, 114, 101, 97, 109, 67, 0, 9, 122, 101, 114, 111, 68, 105, 103, 105,
+ 116, 76, 0, 3, 78, 97, 78, 113, 0, 126, 0, 1, 76, 0, 14, 99, 117, 114, 114, 101,
+ 110, 99, 121, 83, 121, 109, 98, 111, 108, 113, 0, 126, 0, 1, 76, 0, 17, 101, 120, 112,
+ 111, 110, 101, 110, 116, 83, 101, 112, 97, 114, 97, 116, 111, 114, 113, 0, 126, 0, 1, 76,
+ 0, 8, 105, 110, 102, 105, 110, 105, 116, 121, 113, 0, 126, 0, 1, 76, 0, 18, 105, 110,
+ 116, 108, 67, 117, 114, 114, 101, 110, 99, 121, 83, 121, 109, 98, 111, 108, 113, 0, 126, 0,
+ 1, 120, 112, 0, 46, 0, 35, 0, 0, 0, 44, 0, 45, 0, 46, 0, 42, 0, 59, 32,
+ 48, 0, 37, 0, 43, 0, 0, 0, 2, 0, 48, 116, 0, 3, -17, -65, -67, 116, 0, 1,
+ 36, 116, 0, 1, 69, 116, 0, 3, -30, -120, -98, 116, 0, 3, 85, 83, 68,
+ };
+
//NumberFormat.getScientificInstance(Locale.US)
static byte[] scientificInstance = new byte[]{
-84, -19, 0, 5, 115, 114, 0, 30, 99, 111, 109, 46, 105, 98, 109, 46, 105, 99, 117, 46,
1, 69, 116, 0, 3, -30, -120, -98, 116, 0, 3, 85, 83, 68,
};
- static byte[] icu58Latest = {
- -84, -19, 0, 5, 115, 114, 0, 30, 99, 111, 109, 46, 105, 98, 109, 46, 105, 99, 117, 46, 116, 101,
- 120, 116, 46, 68, 101, 99, 105, 109, 97, 108, 70, 111, 114, 109, 97, 116, 11, -1, 3, 98, -40,
- 114, 48, 58, 3, 0, 36, 73, 0, 18, 80, 65, 82, 83, 69, 95, 77, 65, 88, 95, 69, 88, 80, 79, 78,
- 69, 78, 84, 73, 0, 17, 99, 117, 114, 114, 101, 110, 99, 121, 83, 105, 103, 110, 67, 111, 117,
- 110, 116, 90, 0, 27, 100, 101, 99, 105, 109, 97, 108, 83, 101, 112, 97, 114, 97, 116, 111, 114,
- 65, 108, 119, 97, 121, 115, 83, 104, 111, 119, 110, 90, 0, 23, 101, 120, 112, 111, 110, 101,
- 110, 116, 83, 105, 103, 110, 65, 108, 119, 97, 121, 115, 83, 104, 111, 119, 110, 73, 0, 11, 102,
- 111, 114, 109, 97, 116, 87, 105, 100, 116, 104, 66, 0, 12, 103, 114, 111, 117, 112, 105, 110,
- 103, 83, 105, 122, 101, 66, 0, 13, 103, 114, 111, 117, 112, 105, 110, 103, 83, 105, 122, 101,
- 50, 73, 0, 20, 109, 97, 120, 83, 105, 103, 110, 105, 102, 105, 99, 97, 110, 116, 68, 105, 103,
- 105, 116, 115, 66, 0, 17, 109, 105, 110, 69, 120, 112, 111, 110, 101, 110, 116, 68, 105, 103,
- 105, 116, 115, 73, 0, 20, 109, 105, 110, 83, 105, 103, 110, 105, 102, 105, 99, 97, 110, 116, 68,
- 105, 103, 105, 116, 115, 73, 0, 10, 109, 117, 108, 116, 105, 112, 108, 105, 101, 114, 67, 0, 3,
- 112, 97, 100, 73, 0, 11, 112, 97, 100, 80, 111, 115, 105, 116, 105, 111, 110, 90, 0, 15, 112,
- 97, 114, 115, 101, 66, 105, 103, 68, 101, 99, 105, 109, 97, 108, 90, 0, 24, 112, 97, 114, 115,
- 101, 82, 101, 113, 117, 105, 114, 101, 68, 101, 99, 105, 109, 97, 108, 80, 111, 105, 110, 116,
- 73, 0, 12, 114, 111, 117, 110, 100, 105, 110, 103, 77, 111, 100, 101, 73, 0, 21, 115, 101, 114,
- 105, 97, 108, 86, 101, 114, 115, 105, 111, 110, 79, 110, 83, 116, 114, 101, 97, 109, 73, 0, 5,
- 115, 116, 121, 108, 101, 90, 0, 22, 117, 115, 101, 69, 120, 112, 111, 110, 101, 110, 116, 105,
- 97, 108, 78, 111, 116, 97, 116, 105, 111, 110, 90, 0, 20, 117, 115, 101, 83, 105, 103, 110, 105,
- 102, 105, 99, 97, 110, 116, 68, 105, 103, 105, 116, 115, 76, 0, 10, 97, 116, 116, 114, 105, 98,
- 117, 116, 101, 115, 116, 0, 21, 76, 106, 97, 118, 97, 47, 117, 116, 105, 108, 47, 65, 114, 114,
- 97, 121, 76, 105, 115, 116, 59, 76, 0, 14, 99, 117, 114, 114, 101, 110, 99, 121, 67, 104, 111,
- 105, 99, 101, 116, 0, 24, 76, 106, 97, 118, 97, 47, 116, 101, 120, 116, 47, 67, 104, 111, 105,
- 99, 101, 70, 111, 114, 109, 97, 116, 59, 76, 0, 18, 99, 117, 114, 114, 101, 110, 99, 121, 80,
- 108, 117, 114, 97, 108, 73, 110, 102, 111, 116, 0, 37, 76, 99, 111, 109, 47, 105, 98, 109, 47,
- 105, 99, 117, 47, 116, 101, 120, 116, 47, 67, 117, 114, 114, 101, 110, 99, 121, 80, 108, 117,
- 114, 97, 108, 73, 110, 102, 111, 59, 76, 0, 13, 99, 117, 114, 114, 101, 110, 99, 121, 85, 115,
- 97, 103, 101, 116, 0, 41, 76, 99, 111, 109, 47, 105, 98, 109, 47, 105, 99, 117, 47, 117, 116,
- 105, 108, 47, 67, 117, 114, 114, 101, 110, 99, 121, 36, 67, 117, 114, 114, 101, 110, 99, 121,
- 85, 115, 97, 103, 101, 59, 76, 0, 13, 102, 111, 114, 109, 97, 116, 80, 97, 116, 116, 101, 114,
- 110, 116, 0, 18, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103,
- 59, 76, 0, 11, 109, 97, 116, 104, 67, 111, 110, 116, 101, 120, 116, 116, 0, 30, 76, 99, 111,
- 109, 47, 105, 98, 109, 47, 105, 99, 117, 47, 109, 97, 116, 104, 47, 77, 97, 116, 104, 67, 111,
- 110, 116, 101, 120, 116, 59, 76, 0, 16, 110, 101, 103, 80, 114, 101, 102, 105, 120, 80, 97, 116,
- 116, 101, 114, 110, 113, 0, 126, 0, 5, 76, 0, 16, 110, 101, 103, 83, 117, 102, 102, 105, 120,
- 80, 97, 116, 116, 101, 114, 110, 113, 0, 126, 0, 5, 76, 0, 14, 110, 101, 103, 97, 116, 105, 118,
- 101, 80, 114, 101, 102, 105, 120, 113, 0, 126, 0, 5, 76, 0, 14, 110, 101, 103, 97, 116, 105,
- 118, 101, 83, 117, 102, 102, 105, 120, 113, 0, 126, 0, 5, 76, 0, 16, 112, 111, 115, 80, 114,
- 101, 102, 105, 120, 80, 97, 116, 116, 101, 114, 110, 113, 0, 126, 0, 5, 76, 0, 16, 112, 111,
- 115, 83, 117, 102, 102, 105, 120, 80, 97, 116, 116, 101, 114, 110, 113, 0, 126, 0, 5, 76, 0, 14,
- 112, 111, 115, 105, 116, 105, 118, 101, 80, 114, 101, 102, 105, 120, 113, 0, 126, 0, 5, 76, 0,
- 14, 112, 111, 115, 105, 116, 105, 118, 101, 83, 117, 102, 102, 105, 120, 113, 0, 126, 0, 5, 76,
- 0, 17, 114, 111, 117, 110, 100, 105, 110, 103, 73, 110, 99, 114, 101, 109, 101, 110, 116, 116,
- 0, 22, 76, 106, 97, 118, 97, 47, 109, 97, 116, 104, 47, 66, 105, 103, 68, 101, 99, 105, 109, 97,
- 108, 59, 76, 0, 7, 115, 121, 109, 98, 111, 108, 115, 116, 0, 39, 76, 99, 111, 109, 47, 105, 98,
- 109, 47, 105, 99, 117, 47, 116, 101, 120, 116, 47, 68, 101, 99, 105, 109, 97, 108, 70, 111, 114,
- 109, 97, 116, 83, 121, 109, 98, 111, 108, 115, 59, 120, 114, 0, 29, 99, 111, 109, 46, 105, 98,
- 109, 46, 105, 99, 117, 46, 116, 101, 120, 116, 46, 78, 117, 109, 98, 101, 114, 70, 111, 114,
- 109, 97, 116, -33, -10, -77, -65, 19, 125, 7, -24, 3, 0, 14, 90, 0, 12, 103, 114, 111, 117, 112,
- 105, 110, 103, 85, 115, 101, 100, 66, 0, 17, 109, 97, 120, 70, 114, 97, 99, 116, 105, 111, 110,
- 68, 105, 103, 105, 116, 115, 66, 0, 16, 109, 97, 120, 73, 110, 116, 101, 103, 101, 114, 68, 105,
- 103, 105, 116, 115, 73, 0, 21, 109, 97, 120, 105, 109, 117, 109, 70, 114, 97, 99, 116, 105, 111,
- 110, 68, 105, 103, 105, 116, 115, 73, 0, 20, 109, 97, 120, 105, 109, 117, 109, 73, 110, 116,
- 101, 103, 101, 114, 68, 105, 103, 105, 116, 115, 66, 0, 17, 109, 105, 110, 70, 114, 97, 99, 116,
- 105, 111, 110, 68, 105, 103, 105, 116, 115, 66, 0, 16, 109, 105, 110, 73, 110, 116, 101, 103,
- 101, 114, 68, 105, 103, 105, 116, 115, 73, 0, 21, 109, 105, 110, 105, 109, 117, 109, 70, 114,
- 97, 99, 116, 105, 111, 110, 68, 105, 103, 105, 116, 115, 73, 0, 20, 109, 105, 110, 105, 109,
- 117, 109, 73, 110, 116, 101, 103, 101, 114, 68, 105, 103, 105, 116, 115, 90, 0, 16, 112, 97,
- 114, 115, 101, 73, 110, 116, 101, 103, 101, 114, 79, 110, 108, 121, 90, 0, 11, 112, 97, 114,
- 115, 101, 83, 116, 114, 105, 99, 116, 73, 0, 21, 115, 101, 114, 105, 97, 108, 86, 101, 114, 115,
- 105, 111, 110, 79, 110, 83, 116, 114, 101, 97, 109, 76, 0, 21, 99, 97, 112, 105, 116, 97, 108,
- 105, 122, 97, 116, 105, 111, 110, 83, 101, 116, 116, 105, 110, 103, 116, 0, 33, 76, 99, 111,
- 109, 47, 105, 98, 109, 47, 105, 99, 117, 47, 116, 101, 120, 116, 47, 68, 105, 115, 112, 108, 97,
- 121, 67, 111, 110, 116, 101, 120, 116, 59, 76, 0, 8, 99, 117, 114, 114, 101, 110, 99, 121, 116,
- 0, 27, 76, 99, 111, 109, 47, 105, 98, 109, 47, 105, 99, 117, 47, 117, 116, 105, 108, 47, 67,
- 117, 114, 114, 101, 110, 99, 121, 59, 120, 114, 0, 24, 99, 111, 109, 46, 105, 98, 109, 46, 105,
- 99, 117, 46, 116, 101, 120, 116, 46, 85, 70, 111, 114, 109, 97, 116, -69, 26, -15, 32, -39, 7,
- 93, -64, 2, 0, 2, 76, 0, 12, 97, 99, 116, 117, 97, 108, 76, 111, 99, 97, 108, 101, 116, 0, 26,
- 76, 99, 111, 109, 47, 105, 98, 109, 47, 105, 99, 117, 47, 117, 116, 105, 108, 47, 85, 76, 111,
- 99, 97, 108, 101, 59, 76, 0, 11, 118, 97, 108, 105, 100, 76, 111, 99, 97, 108, 101, 113, 0, 126,
- 0, 13, 120, 114, 0, 16, 106, 97, 118, 97, 46, 116, 101, 120, 116, 46, 70, 111, 114, 109, 97,
- 116, -5, -40, -68, 18, -23, 15, 24, 67, 2, 0, 0, 120, 112, 112, 112, 1, 3, 127, 0, 0, 0, 3, 0,
- 0, 1, 53, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 2, 126, 114, 0, 31, 99, 111, 109, 46,
- 105, 98, 109, 46, 105, 99, 117, 46, 116, 101, 120, 116, 46, 68, 105, 115, 112, 108, 97, 121, 67,
- 111, 110, 116, 101, 120, 116, 0, 0, 0, 0, 0, 0, 0, 0, 18, 0, 0, 120, 114, 0, 14, 106, 97, 118,
- 97, 46, 108, 97, 110, 103, 46, 69, 110, 117, 109, 0, 0, 0, 0, 0, 0, 0, 0, 18, 0, 0, 120, 112,
- 116, 0, 19, 67, 65, 80, 73, 84, 65, 76, 73, 90, 65, 84, 73, 79, 78, 95, 78, 79, 78, 69, 115,
- 114, 0, 45, 99, 111, 109, 46, 105, 98, 109, 46, 105, 99, 117, 46, 117, 116, 105, 108, 46, 77,
- 101, 97, 115, 117, 114, 101, 85, 110, 105, 116, 36, 77, 101, 97, 115, 117, 114, 101, 85, 110,
- 105, 116, 80, 114, 111, 120, 121, -55, -70, 119, -8, -15, 121, 121, -30, 12, 0, 0, 120, 112,
- 119, 18, 0, 0, 8, 99, 117, 114, 114, 101, 110, 99, 121, 0, 3, 85, 83, 68, 0, 0, 120, 120, 0, 0,
- 3, -24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 6, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 32, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 115, 114, 0, 19, 106, 97, 118, 97, 46,
- 117, 116, 105, 108, 46, 65, 114, 114, 97, 121, 76, 105, 115, 116, 120, -127, -46, 29, -103, -57,
- 97, -99, 3, 0, 1, 73, 0, 4, 115, 105, 122, 101, 120, 112, 0, 0, 0, 0, 119, 4, 0, 0, 0, 0, 120,
- 112, 112, 126, 114, 0, 39, 99, 111, 109, 46, 105, 98, 109, 46, 105, 99, 117, 46, 117, 116, 105,
- 108, 46, 67, 117, 114, 114, 101, 110, 99, 121, 36, 67, 117, 114, 114, 101, 110, 99, 121, 85,
- 115, 97, 103, 101, 0, 0, 0, 0, 0, 0, 0, 0, 18, 0, 0, 120, 113, 0, 126, 0, 17, 116, 0, 8, 83, 84,
- 65, 78, 68, 65, 82, 68, 116, 0, 9, 35, 44, 35, 35, 48, 46, 35, 35, 35, 115, 114, 0, 28, 99, 111,
- 109, 46, 105, 98, 109, 46, 105, 99, 117, 46, 109, 97, 116, 104, 46, 77, 97, 116, 104, 67, 111,
- 110, 116, 101, 120, 116, 99, 105, 109, 109, 99, 49, 48, 48, 2, 0, 4, 73, 0, 6, 100, 105, 103,
- 105, 116, 115, 73, 0, 4, 102, 111, 114, 109, 90, 0, 10, 108, 111, 115, 116, 68, 105, 103, 105,
- 116, 115, 73, 0, 12, 114, 111, 117, 110, 100, 105, 110, 103, 77, 111, 100, 101, 120, 112, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 116, 0, 1, 45, 116, 0, 0, 116, 0, 1, 45, 116, 0, 0, 116, 0, 0,
- 113, 0, 126, 0, 31, 116, 0, 0, 116, 0, 0, 112, 115, 114, 0, 37, 99, 111, 109, 46, 105, 98, 109,
- 46, 105, 99, 117, 46, 116, 101, 120, 116, 46, 68, 101, 99, 105, 109, 97, 108, 70, 111, 114, 109,
- 97, 116, 83, 121, 109, 98, 111, 108, 115, 80, 29, 23, -103, 8, 104, -109, -100, 2, 0, 38, 67, 0,
- 16, 100, 101, 99, 105, 109, 97, 108, 83, 101, 112, 97, 114, 97, 116, 111, 114, 67, 0, 5, 100,
- 105, 103, 105, 116, 67, 0, 11, 101, 120, 112, 111, 110, 101, 110, 116, 105, 97, 108, 67, 0, 17,
- 103, 114, 111, 117, 112, 105, 110, 103, 83, 101, 112, 97, 114, 97, 116, 111, 114, 67, 0, 9, 109,
- 105, 110, 117, 115, 83, 105, 103, 110, 67, 0, 25, 109, 111, 110, 101, 116, 97, 114, 121, 71,
- 114, 111, 117, 112, 105, 110, 103, 83, 101, 112, 97, 114, 97, 116, 111, 114, 67, 0, 17, 109,
- 111, 110, 101, 116, 97, 114, 121, 83, 101, 112, 97, 114, 97, 116, 111, 114, 67, 0, 9, 112, 97,
- 100, 69, 115, 99, 97, 112, 101, 67, 0, 16, 112, 97, 116, 116, 101, 114, 110, 83, 101, 112, 97,
- 114, 97, 116, 111, 114, 67, 0, 7, 112, 101, 114, 77, 105, 108, 108, 67, 0, 7, 112, 101, 114, 99,
- 101, 110, 116, 67, 0, 8, 112, 108, 117, 115, 83, 105, 103, 110, 73, 0, 21, 115, 101, 114, 105,
- 97, 108, 86, 101, 114, 115, 105, 111, 110, 79, 110, 83, 116, 114, 101, 97, 109, 67, 0, 8, 115,
- 105, 103, 68, 105, 103, 105, 116, 67, 0, 9, 122, 101, 114, 111, 68, 105, 103, 105, 116, 76, 0,
- 3, 78, 97, 78, 113, 0, 126, 0, 5, 76, 0, 12, 97, 99, 116, 117, 97, 108, 76, 111, 99, 97, 108,
- 101, 113, 0, 126, 0, 13, 76, 0, 15, 99, 117, 114, 114, 101, 110, 99, 121, 80, 97, 116, 116, 101,
- 114, 110, 113, 0, 126, 0, 5, 91, 0, 19, 99, 117, 114, 114, 101, 110, 99, 121, 83, 112, 99, 65,
- 102, 116, 101, 114, 83, 121, 109, 116, 0, 19, 91, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103,
- 47, 83, 116, 114, 105, 110, 103, 59, 91, 0, 20, 99, 117, 114, 114, 101, 110, 99, 121, 83, 112,
- 99, 66, 101, 102, 111, 114, 101, 83, 121, 109, 113, 0, 126, 0, 38, 76, 0, 14, 99, 117, 114, 114,
- 101, 110, 99, 121, 83, 121, 109, 98, 111, 108, 113, 0, 126, 0, 5, 76, 0, 22, 100, 101, 99, 105,
- 109, 97, 108, 83, 101, 112, 97, 114, 97, 116, 111, 114, 83, 116, 114, 105, 110, 103, 113, 0,
- 126, 0, 5, 91, 0, 12, 100, 105, 103, 105, 116, 83, 116, 114, 105, 110, 103, 115, 113, 0, 126, 0,
- 38, 91, 0, 6, 100, 105, 103, 105, 116, 115, 116, 0, 2, 91, 67, 76, 0, 26, 101, 120, 112, 111,
- 110, 101, 110, 116, 77, 117, 108, 116, 105, 112, 108, 105, 99, 97, 116, 105, 111, 110, 83, 105,
- 103, 110, 113, 0, 126, 0, 5, 76, 0, 17, 101, 120, 112, 111, 110, 101, 110, 116, 83, 101, 112,
- 97, 114, 97, 116, 111, 114, 113, 0, 126, 0, 5, 76, 0, 23, 103, 114, 111, 117, 112, 105, 110,
- 103, 83, 101, 112, 97, 114, 97, 116, 111, 114, 83, 116, 114, 105, 110, 103, 113, 0, 126, 0, 5,
- 76, 0, 8, 105, 110, 102, 105, 110, 105, 116, 121, 113, 0, 126, 0, 5, 76, 0, 18, 105, 110, 116,
- 108, 67, 117, 114, 114, 101, 110, 99, 121, 83, 121, 109, 98, 111, 108, 113, 0, 126, 0, 5, 76, 0,
- 11, 109, 105, 110, 117, 115, 83, 116, 114, 105, 110, 103, 113, 0, 126, 0, 5, 76, 0, 31, 109,
- 111, 110, 101, 116, 97, 114, 121, 71, 114, 111, 117, 112, 105, 110, 103, 83, 101, 112, 97, 114,
- 97, 116, 111, 114, 83, 116, 114, 105, 110, 103, 113, 0, 126, 0, 5, 76, 0, 23, 109, 111, 110,
- 101, 116, 97, 114, 121, 83, 101, 112, 97, 114, 97, 116, 111, 114, 83, 116, 114, 105, 110, 103,
- 113, 0, 126, 0, 5, 76, 0, 13, 112, 101, 114, 77, 105, 108, 108, 83, 116, 114, 105, 110, 103,
- 113, 0, 126, 0, 5, 76, 0, 13, 112, 101, 114, 99, 101, 110, 116, 83, 116, 114, 105, 110, 103,
- 113, 0, 126, 0, 5, 76, 0, 10, 112, 108, 117, 115, 83, 116, 114, 105, 110, 103, 113, 0, 126, 0,
- 5, 76, 0, 15, 114, 101, 113, 117, 101, 115, 116, 101, 100, 76, 111, 99, 97, 108, 101, 116, 0,
- 18, 76, 106, 97, 118, 97, 47, 117, 116, 105, 108, 47, 76, 111, 99, 97, 108, 101, 59, 76, 0, 7,
- 117, 108, 111, 99, 97, 108, 101, 113, 0, 126, 0, 13, 76, 0, 11, 118, 97, 108, 105, 100, 76, 111,
- 99, 97, 108, 101, 113, 0, 126, 0, 13, 120, 112, 0, 46, 0, 35, 0, 0, 0, 44, 0, 45, 0, 44, 0, 46,
- 0, 42, 0, 59, 32, 48, 0, 37, 0, 43, 0, 0, 0, 8, 0, 64, 0, 48, 116, 0, 3, 78, 97, 78, 115, 114,
- 0, 24, 99, 111, 109, 46, 105, 98, 109, 46, 105, 99, 117, 46, 117, 116, 105, 108, 46, 85, 76,
- 111, 99, 97, 108, 101, 51, -114, -10, 104, 70, -48, 11, -31, 2, 0, 1, 76, 0, 8, 108, 111, 99,
- 97, 108, 101, 73, 68, 113, 0, 126, 0, 5, 120, 112, 116, 0, 5, 101, 110, 95, 85, 83, 112, 117,
- 114, 0, 19, 91, 76, 106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 83, 116, 114, 105, 110, 103,
- 59, -83, -46, 86, -25, -23, 29, 123, 71, 2, 0, 0, 120, 112, 0, 0, 0, 3, 116, 0, 6, 91, 58, 94,
- 83, 58, 93, 116, 0, 9, 91, 58, 100, 105, 103, 105, 116, 58, 93, 116, 0, 2, -62, -96, 117, 113,
- 0, 126, 0, 46, 0, 0, 0, 3, 113, 0, 126, 0, 48, 113, 0, 126, 0, 49, 113, 0, 126, 0, 50, 116, 0,
- 1, 36, 116, 0, 1, 46, 117, 113, 0, 126, 0, 46, 0, 0, 0, 10, 116, 0, 1, 48, 116, 0, 1, 49, 116,
- 0, 1, 50, 116, 0, 1, 51, 116, 0, 1, 52, 116, 0, 1, 53, 116, 0, 1, 54, 116, 0, 1, 55, 116, 0, 1,
- 56, 116, 0, 1, 57, 117, 114, 0, 2, 91, 67, -80, 38, 102, -80, -30, 93, -124, -84, 2, 0, 0, 120,
- 112, 0, 0, 0, 10, 0, 48, 0, 49, 0, 50, 0, 51, 0, 52, 0, 53, 0, 54, 0, 55, 0, 56, 0, 57, 116, 0,
- 2, -61, -105, 116, 0, 1, 69, 116, 0, 1, 44, 116, 0, 3, -30, -120, -98, 116, 0, 3, 85, 83, 68,
- 116, 0, 1, 45, 113, 0, 126, 0, 69, 113, 0, 126, 0, 53, 116, 0, 3, -30, -128, -80, 116, 0, 1, 37,
- 116, 0, 1, 43, 115, 114, 0, 16, 106, 97, 118, 97, 46, 117, 116, 105, 108, 46, 76, 111, 99, 97,
- 108, 101, 126, -8, 17, 96, -100, 48, -7, -20, 3, 0, 6, 73, 0, 8, 104, 97, 115, 104, 99, 111,
- 100, 101, 76, 0, 7, 99, 111, 117, 110, 116, 114, 121, 113, 0, 126, 0, 5, 76, 0, 10, 101, 120,
- 116, 101, 110, 115, 105, 111, 110, 115, 113, 0, 126, 0, 5, 76, 0, 8, 108, 97, 110, 103, 117, 97,
- 103, 101, 113, 0, 126, 0, 5, 76, 0, 6, 115, 99, 114, 105, 112, 116, 113, 0, 126, 0, 5, 76, 0, 7,
- 118, 97, 114, 105, 97, 110, 116, 113, 0, 126, 0, 5, 120, 112, -1, -1, -1, -1, 116, 0, 2, 85, 83,
- 116, 0, 0, 116, 0, 2, 101, 110, 113, 0, 126, 0, 79, 113, 0, 126, 0, 79, 120, 115, 113, 0, 126,
- 0, 43, 113, 0, 126, 0, 45, 113, 0, 126, 0, 44, 120
- };
-
- static byte[] newFromPattern = {
- -84, -19, 0, 5, 115, 114, 0, 30, 99, 111, 109, 46, 105, 98, 109, 46, 105, 99, 117, 46, 116, 101,
- 120, 116, 46, 68, 101, 99, 105, 109, 97, 108, 70, 111, 114, 109, 97, 116, 11, -1, 3, 98, -40,
- 114, 48, 58, 3, 0, 1, 73, 0, 18, 105, 99, 117, 77, 97, 116, 104, 67, 111, 110, 116, 101, 120,
- 116, 70, 111, 114, 109, 120, 114, 0, 29, 99, 111, 109, 46, 105, 98, 109, 46, 105, 99, 117, 46,
- 116, 101, 120, 116, 46, 78, 117, 109, 98, 101, 114, 70, 111, 114, 109, 97, 116, -33, -10, -77,
- -65, 19, 125, 7, -24, 3, 0, 14, 90, 0, 12, 103, 114, 111, 117, 112, 105, 110, 103, 85, 115, 101,
- 100, 66, 0, 17, 109, 97, 120, 70, 114, 97, 99, 116, 105, 111, 110, 68, 105, 103, 105, 116, 115,
- 66, 0, 16, 109, 97, 120, 73, 110, 116, 101, 103, 101, 114, 68, 105, 103, 105, 116, 115, 73, 0,
- 21, 109, 97, 120, 105, 109, 117, 109, 70, 114, 97, 99, 116, 105, 111, 110, 68, 105, 103, 105,
- 116, 115, 73, 0, 20, 109, 97, 120, 105, 109, 117, 109, 73, 110, 116, 101, 103, 101, 114, 68,
- 105, 103, 105, 116, 115, 66, 0, 17, 109, 105, 110, 70, 114, 97, 99, 116, 105, 111, 110, 68, 105,
- 103, 105, 116, 115, 66, 0, 16, 109, 105, 110, 73, 110, 116, 101, 103, 101, 114, 68, 105, 103,
- 105, 116, 115, 73, 0, 21, 109, 105, 110, 105, 109, 117, 109, 70, 114, 97, 99, 116, 105, 111,
- 110, 68, 105, 103, 105, 116, 115, 73, 0, 20, 109, 105, 110, 105, 109, 117, 109, 73, 110, 116,
- 101, 103, 101, 114, 68, 105, 103, 105, 116, 115, 90, 0, 16, 112, 97, 114, 115, 101, 73, 110,
- 116, 101, 103, 101, 114, 79, 110, 108, 121, 90, 0, 11, 112, 97, 114, 115, 101, 83, 116, 114,
- 105, 99, 116, 73, 0, 21, 115, 101, 114, 105, 97, 108, 86, 101, 114, 115, 105, 111, 110, 79, 110,
- 83, 116, 114, 101, 97, 109, 76, 0, 21, 99, 97, 112, 105, 116, 97, 108, 105, 122, 97, 116, 105,
- 111, 110, 83, 101, 116, 116, 105, 110, 103, 116, 0, 33, 76, 99, 111, 109, 47, 105, 98, 109, 47,
- 105, 99, 117, 47, 116, 101, 120, 116, 47, 68, 105, 115, 112, 108, 97, 121, 67, 111, 110, 116,
- 101, 120, 116, 59, 76, 0, 8, 99, 117, 114, 114, 101, 110, 99, 121, 116, 0, 27, 76, 99, 111, 109,
- 47, 105, 98, 109, 47, 105, 99, 117, 47, 117, 116, 105, 108, 47, 67, 117, 114, 114, 101, 110, 99,
- 121, 59, 120, 114, 0, 24, 99, 111, 109, 46, 105, 98, 109, 46, 105, 99, 117, 46, 116, 101, 120,
- 116, 46, 85, 70, 111, 114, 109, 97, 116, -69, 26, -15, 32, -39, 7, 93, -64, 2, 0, 2, 76, 0, 12,
- 97, 99, 116, 117, 97, 108, 76, 111, 99, 97, 108, 101, 116, 0, 26, 76, 99, 111, 109, 47, 105, 98,
- 109, 47, 105, 99, 117, 47, 117, 116, 105, 108, 47, 85, 76, 111, 99, 97, 108, 101, 59, 76, 0, 11,
- 118, 97, 108, 105, 100, 76, 111, 99, 97, 108, 101, 113, 0, 126, 0, 5, 120, 114, 0, 16, 106, 97,
- 118, 97, 46, 116, 101, 120, 116, 46, 70, 111, 114, 109, 97, 116, -5, -40, -68, 18, -23, 15, 24,
- 67, 2, 0, 0, 120, 112, 112, 112, 1, 3, 40, 0, 0, 0, 3, 0, 0, 0, 40, 0, 1, 0, 0, 0, 0, 0, 0, 0,
- 1, 0, 0, 0, 0, 0, 2, 126, 114, 0, 31, 99, 111, 109, 46, 105, 98, 109, 46, 105, 99, 117, 46, 116,
- 101, 120, 116, 46, 68, 105, 115, 112, 108, 97, 121, 67, 111, 110, 116, 101, 120, 116, 0, 0, 0,
- 0, 0, 0, 0, 0, 18, 0, 0, 120, 114, 0, 14, 106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 69, 110,
- 117, 109, 0, 0, 0, 0, 0, 0, 0, 0, 18, 0, 0, 120, 112, 116, 0, 19, 67, 65, 80, 73, 84, 65, 76,
- 73, 90, 65, 84, 73, 79, 78, 95, 78, 79, 78, 69, 112, 120, 0, 0, 0, 0, 119, 4, 0, 0, 0, 0, 115,
- 114, 0, 34, 99, 111, 109, 46, 105, 98, 109, 46, 105, 99, 117, 46, 105, 109, 112, 108, 46, 110,
- 117, 109, 98, 101, 114, 46, 80, 114, 111, 112, 101, 114, 116, 105, 101, 115, 56, -42, 52, -54,
- -104, -87, -46, 123, 3, 0, 0, 120, 112, 119, 8, 0, 0, 0, 0, 0, 0, 0, 7, 116, 0, 12, 103, 114,
- 111, 117, 112, 105, 110, 103, 83, 105, 122, 101, 115, 114, 0, 17, 106, 97, 118, 97, 46, 108, 97,
- 110, 103, 46, 73, 110, 116, 101, 103, 101, 114, 18, -30, -96, -92, -9, -127, -121, 56, 2, 0, 1,
- 73, 0, 5, 118, 97, 108, 117, 101, 120, 114, 0, 16, 106, 97, 118, 97, 46, 108, 97, 110, 103, 46,
- 78, 117, 109, 98, 101, 114, -122, -84, -107, 29, 11, -108, -32, -117, 2, 0, 0, 120, 112, 0, 0,
- 0, 3, 116, 0, 20, 109, 105, 110, 105, 109, 117, 109, 73, 110, 116, 101, 103, 101, 114, 68, 105,
- 103, 105, 116, 115, 115, 113, 0, 126, 0, 15, 0, 0, 0, 2, 116, 0, 15, 112, 97, 100, 100, 105,
- 110, 103, 76, 111, 99, 97, 116, 105, 111, 110, 126, 114, 0, 64, 99, 111, 109, 46, 105, 98, 109,
- 46, 105, 99, 117, 46, 105, 109, 112, 108, 46, 110, 117, 109, 98, 101, 114, 46, 102, 111, 114,
- 109, 97, 116, 116, 101, 114, 115, 46, 80, 97, 100, 100, 105, 110, 103, 70, 111, 114, 109, 97,
- 116, 36, 80, 97, 100, 100, 105, 110, 103, 76, 111, 99, 97, 116, 105, 111, 110, 0, 0, 0, 0, 0, 0,
- 0, 0, 18, 0, 0, 120, 113, 0, 126, 0, 9, 116, 0, 12, 65, 70, 84, 69, 82, 95, 80, 82, 69, 70, 73,
- 88, 116, 0, 13, 112, 97, 100, 100, 105, 110, 103, 83, 116, 114, 105, 110, 103, 116, 0, 1, 42,
- 116, 0, 12, 112, 97, 100, 100, 105, 110, 103, 87, 105, 100, 116, 104, 115, 113, 0, 126, 0, 15,
- 0, 0, 0, 16, 116, 0, 21, 112, 111, 115, 105, 116, 105, 118, 101, 80, 114, 101, 102, 105, 120,
- 80, 97, 116, 116, 101, 114, 110, 116, 0, 2, 65, 45, 116, 0, 21, 112, 111, 115, 105, 116, 105,
- 118, 101, 83, 117, 102, 102, 105, 120, 80, 97, 116, 116, 101, 114, 110, 116, 0, 3, 98, -62, -92,
- 120, 115, 114, 0, 37, 99, 111, 109, 46, 105, 98, 109, 46, 105, 99, 117, 46, 116, 101, 120, 116,
- 46, 68, 101, 99, 105, 109, 97, 108, 70, 111, 114, 109, 97, 116, 83, 121, 109, 98, 111, 108, 115,
- 80, 29, 23, -103, 8, 104, -109, -100, 2, 0, 38, 67, 0, 16, 100, 101, 99, 105, 109, 97, 108, 83,
- 101, 112, 97, 114, 97, 116, 111, 114, 67, 0, 5, 100, 105, 103, 105, 116, 67, 0, 11, 101, 120,
- 112, 111, 110, 101, 110, 116, 105, 97, 108, 67, 0, 17, 103, 114, 111, 117, 112, 105, 110, 103,
- 83, 101, 112, 97, 114, 97, 116, 111, 114, 67, 0, 9, 109, 105, 110, 117, 115, 83, 105, 103, 110,
- 67, 0, 25, 109, 111, 110, 101, 116, 97, 114, 121, 71, 114, 111, 117, 112, 105, 110, 103, 83,
- 101, 112, 97, 114, 97, 116, 111, 114, 67, 0, 17, 109, 111, 110, 101, 116, 97, 114, 121, 83, 101,
- 112, 97, 114, 97, 116, 111, 114, 67, 0, 9, 112, 97, 100, 69, 115, 99, 97, 112, 101, 67, 0, 16,
- 112, 97, 116, 116, 101, 114, 110, 83, 101, 112, 97, 114, 97, 116, 111, 114, 67, 0, 7, 112, 101,
- 114, 77, 105, 108, 108, 67, 0, 7, 112, 101, 114, 99, 101, 110, 116, 67, 0, 8, 112, 108, 117,
- 115, 83, 105, 103, 110, 73, 0, 21, 115, 101, 114, 105, 97, 108, 86, 101, 114, 115, 105, 111,
- 110, 79, 110, 83, 116, 114, 101, 97, 109, 67, 0, 8, 115, 105, 103, 68, 105, 103, 105, 116, 67,
- 0, 9, 122, 101, 114, 111, 68, 105, 103, 105, 116, 76, 0, 3, 78, 97, 78, 116, 0, 18, 76, 106, 97,
- 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 76, 0, 12, 97, 99, 116,
- 117, 97, 108, 76, 111, 99, 97, 108, 101, 113, 0, 126, 0, 5, 76, 0, 15, 99, 117, 114, 114, 101,
- 110, 99, 121, 80, 97, 116, 116, 101, 114, 110, 113, 0, 126, 0, 33, 91, 0, 19, 99, 117, 114, 114,
- 101, 110, 99, 121, 83, 112, 99, 65, 102, 116, 101, 114, 83, 121, 109, 116, 0, 19, 91, 76, 106,
- 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 91, 0, 20, 99, 117,
- 114, 114, 101, 110, 99, 121, 83, 112, 99, 66, 101, 102, 111, 114, 101, 83, 121, 109, 113, 0,
- 126, 0, 34, 76, 0, 14, 99, 117, 114, 114, 101, 110, 99, 121, 83, 121, 109, 98, 111, 108, 113, 0,
- 126, 0, 33, 76, 0, 22, 100, 101, 99, 105, 109, 97, 108, 83, 101, 112, 97, 114, 97, 116, 111,
- 114, 83, 116, 114, 105, 110, 103, 113, 0, 126, 0, 33, 91, 0, 12, 100, 105, 103, 105, 116, 83,
- 116, 114, 105, 110, 103, 115, 113, 0, 126, 0, 34, 91, 0, 6, 100, 105, 103, 105, 116, 115, 116,
- 0, 2, 91, 67, 76, 0, 26, 101, 120, 112, 111, 110, 101, 110, 116, 77, 117, 108, 116, 105, 112,
- 108, 105, 99, 97, 116, 105, 111, 110, 83, 105, 103, 110, 113, 0, 126, 0, 33, 76, 0, 17, 101,
- 120, 112, 111, 110, 101, 110, 116, 83, 101, 112, 97, 114, 97, 116, 111, 114, 113, 0, 126, 0, 33,
- 76, 0, 23, 103, 114, 111, 117, 112, 105, 110, 103, 83, 101, 112, 97, 114, 97, 116, 111, 114, 83,
- 116, 114, 105, 110, 103, 113, 0, 126, 0, 33, 76, 0, 8, 105, 110, 102, 105, 110, 105, 116, 121,
- 113, 0, 126, 0, 33, 76, 0, 18, 105, 110, 116, 108, 67, 117, 114, 114, 101, 110, 99, 121, 83,
- 121, 109, 98, 111, 108, 113, 0, 126, 0, 33, 76, 0, 11, 109, 105, 110, 117, 115, 83, 116, 114,
- 105, 110, 103, 113, 0, 126, 0, 33, 76, 0, 31, 109, 111, 110, 101, 116, 97, 114, 121, 71, 114,
- 111, 117, 112, 105, 110, 103, 83, 101, 112, 97, 114, 97, 116, 111, 114, 83, 116, 114, 105, 110,
- 103, 113, 0, 126, 0, 33, 76, 0, 23, 109, 111, 110, 101, 116, 97, 114, 121, 83, 101, 112, 97,
- 114, 97, 116, 111, 114, 83, 116, 114, 105, 110, 103, 113, 0, 126, 0, 33, 76, 0, 13, 112, 101,
- 114, 77, 105, 108, 108, 83, 116, 114, 105, 110, 103, 113, 0, 126, 0, 33, 76, 0, 13, 112, 101,
- 114, 99, 101, 110, 116, 83, 116, 114, 105, 110, 103, 113, 0, 126, 0, 33, 76, 0, 10, 112, 108,
- 117, 115, 83, 116, 114, 105, 110, 103, 113, 0, 126, 0, 33, 76, 0, 15, 114, 101, 113, 117, 101,
- 115, 116, 101, 100, 76, 111, 99, 97, 108, 101, 116, 0, 18, 76, 106, 97, 118, 97, 47, 117, 116,
- 105, 108, 47, 76, 111, 99, 97, 108, 101, 59, 76, 0, 7, 117, 108, 111, 99, 97, 108, 101, 113, 0,
- 126, 0, 5, 76, 0, 11, 118, 97, 108, 105, 100, 76, 111, 99, 97, 108, 101, 113, 0, 126, 0, 5, 120,
- 112, 0, 46, 0, 35, 0, 0, 0, 44, 0, 45, 0, 44, 0, 46, 0, 42, 0, 59, 32, 48, 0, 37, 0, 43, 0, 0,
- 0, 8, 0, 64, 0, 48, 116, 0, 3, 78, 97, 78, 115, 114, 0, 24, 99, 111, 109, 46, 105, 98, 109, 46,
- 105, 99, 117, 46, 117, 116, 105, 108, 46, 85, 76, 111, 99, 97, 108, 101, 51, -114, -10, 104, 70,
- -48, 11, -31, 2, 0, 1, 76, 0, 8, 108, 111, 99, 97, 108, 101, 73, 68, 113, 0, 126, 0, 33, 120,
- 112, 116, 0, 5, 101, 110, 95, 85, 83, 112, 117, 114, 0, 19, 91, 76, 106, 97, 118, 97, 46, 108,
- 97, 110, 103, 46, 83, 116, 114, 105, 110, 103, 59, -83, -46, 86, -25, -23, 29, 123, 71, 2, 0, 0,
- 120, 112, 0, 0, 0, 3, 116, 0, 6, 91, 58, 94, 83, 58, 93, 116, 0, 9, 91, 58, 100, 105, 103, 105,
- 116, 58, 93, 116, 0, 2, -62, -96, 117, 113, 0, 126, 0, 42, 0, 0, 0, 3, 113, 0, 126, 0, 44, 113,
- 0, 126, 0, 45, 113, 0, 126, 0, 46, 116, 0, 1, 36, 116, 0, 1, 46, 117, 113, 0, 126, 0, 42, 0, 0,
- 0, 10, 116, 0, 1, 48, 116, 0, 1, 49, 116, 0, 1, 50, 116, 0, 1, 51, 116, 0, 1, 52, 116, 0, 1, 53,
- 116, 0, 1, 54, 116, 0, 1, 55, 116, 0, 1, 56, 116, 0, 1, 57, 117, 114, 0, 2, 91, 67, -80, 38,
- 102, -80, -30, 93, -124, -84, 2, 0, 0, 120, 112, 0, 0, 0, 10, 0, 48, 0, 49, 0, 50, 0, 51, 0, 52,
- 0, 53, 0, 54, 0, 55, 0, 56, 0, 57, 116, 0, 2, -61, -105, 116, 0, 1, 69, 116, 0, 1, 44, 116, 0,
- 3, -30, -120, -98, 116, 0, 3, 85, 83, 68, 116, 0, 1, 45, 113, 0, 126, 0, 65, 113, 0, 126, 0, 49,
- 116, 0, 3, -30, -128, -80, 116, 0, 1, 37, 116, 0, 1, 43, 115, 114, 0, 16, 106, 97, 118, 97, 46,
- 117, 116, 105, 108, 46, 76, 111, 99, 97, 108, 101, 126, -8, 17, 96, -100, 48, -7, -20, 3, 0, 6,
- 73, 0, 8, 104, 97, 115, 104, 99, 111, 100, 101, 76, 0, 7, 99, 111, 117, 110, 116, 114, 121, 113,
- 0, 126, 0, 33, 76, 0, 10, 101, 120, 116, 101, 110, 115, 105, 111, 110, 115, 113, 0, 126, 0, 33,
- 76, 0, 8, 108, 97, 110, 103, 117, 97, 103, 101, 113, 0, 126, 0, 33, 76, 0, 6, 115, 99, 114, 105,
- 112, 116, 113, 0, 126, 0, 33, 76, 0, 7, 118, 97, 114, 105, 97, 110, 116, 113, 0, 126, 0, 33,
- 120, 112, -1, -1, -1, -1, 116, 0, 2, 85, 83, 116, 0, 0, 116, 0, 2, 101, 110, 113, 0, 126, 0, 75,
- 113, 0, 126, 0, 75, 120, 115, 113, 0, 126, 0, 39, 113, 0, 126, 0, 41, 113, 0, 126, 0, 40, 120
- };
-
- final static byte[][] content = {
- generalInstance,
- currencyInstance,
- scientificInstance,
- icu58Latest,
- newFromPattern
- };
+ final static byte[][] content = {generalInstance, currencyInstance, percentInstance, scientificInstance};
}
package com.ibm.icu.dev.test.format;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
import java.math.BigInteger;
-import java.math.RoundingMode;
import java.text.AttributedCharacterIterator;
import java.text.FieldPosition;
import java.text.Format;
import com.ibm.icu.impl.LocaleUtility;
import com.ibm.icu.impl.data.ResourceReader;
import com.ibm.icu.impl.data.TokenIterator;
-import com.ibm.icu.impl.number.rounders.SignificantDigitsRounder.SignificantDigitsMode;
import com.ibm.icu.math.BigDecimal;
import com.ibm.icu.math.MathContext;
import com.ibm.icu.text.CompactDecimalFormat;
import com.ibm.icu.text.NumberingSystem;
import com.ibm.icu.text.RuleBasedNumberFormat;
import com.ibm.icu.util.Currency;
-import com.ibm.icu.util.Currency.CurrencyUsage;
import com.ibm.icu.util.CurrencyAmount;
import com.ibm.icu.util.ULocale;
public class NumberFormatTest extends TestFmwk {
+ private static ULocale EN = new ULocale("en");
+
+ private static Number toNumber(String s) {
+ if (s.equals("NaN")) {
+ return Double.NaN;
+ } else if (s.equals("-Inf")) {
+ return Double.NEGATIVE_INFINITY;
+ } else if (s.equals("Inf")) {
+ return Double.POSITIVE_INFINITY;
+ }
+ return new BigDecimal(s);
+ }
+
+
+ private DataDrivenNumberFormatTestUtility.CodeUnderTest ICU =
+ new DataDrivenNumberFormatTestUtility.CodeUnderTest() {
+ @Override
+ public Character Id() { return 'J'; }
+
+ @Override
+ public String format(NumberFormatTestData tuple) {
+ DecimalFormat fmt = newDecimalFormat(tuple);
+ String actual = fmt.format(toNumber(tuple.format));
+ String expected = tuple.output;
+ if (!expected.equals(actual)) {
+ return "Expected " + expected + ", got " + actual;
+ }
+ return null;
+ }
+
+ @Override
+ public String toPattern(NumberFormatTestData tuple) {
+ DecimalFormat fmt = newDecimalFormat(tuple);
+ StringBuilder result = new StringBuilder();
+ if (tuple.toPattern != null) {
+ String expected = tuple.toPattern;
+ String actual = fmt.toPattern();
+ if (!expected.equals(actual)) {
+ result.append("Expected toPattern=" + expected + ", got " + actual);
+ }
+ }
+ if (tuple.toLocalizedPattern != null) {
+ String expected = tuple.toLocalizedPattern;
+ String actual = fmt.toLocalizedPattern();
+ if (!expected.equals(actual)) {
+ result.append("Expected toLocalizedPattern=" + expected + ", got " + actual);
+ }
+ }
+ return result.length() == 0 ? null : result.toString();
+ }
+
+ @Override
+ public String parse(NumberFormatTestData tuple) {
+ DecimalFormat fmt = newDecimalFormat(tuple);
+ ParsePosition ppos = new ParsePosition(0);
+ Number actual = fmt.parse(tuple.parse, ppos);
+ if (ppos.getIndex() == 0) {
+ if (!tuple.output.equals("fail")) {
+ return "Parse error expected.";
+ }
+ return null;
+ }
+ if (tuple.output.equals("fail")) {
+ return "Parse succeeded: "+actual+", but was expected to fail.";
+ }
+ Number expected = toNumber(tuple.output);
+ // number types cannot be compared, this is the best we can do.
+ if (expected.doubleValue() != (actual.doubleValue())) {
+ return "Expected: " + expected + ", got: " + actual;
+ }
+ return null;
+ }
+
+ @Override
+ public String parseCurrency(NumberFormatTestData tuple) {
+ DecimalFormat fmt = newDecimalFormat(tuple);
+ ParsePosition ppos = new ParsePosition(0);
+ CurrencyAmount currAmt = fmt.parseCurrency(tuple.parse, ppos);
+ if (ppos.getIndex() == 0) {
+ if (!tuple.output.equals("fail")) {
+ return "Parse error expected.";
+ }
+ return null;
+ }
+ if (tuple.output.equals("fail")) {
+ return "Parse succeeded: "+currAmt+", but was expected to fail.";
+ }
+ Number expected = toNumber(tuple.output);
+ Number actual = currAmt.getNumber();
+ // number types cannot be compared, this is the best we can do.
+ if (expected.doubleValue() != (actual.doubleValue())) {
+ return "Expected: " + expected + ", got: " + actual;
+ }
+
+ if (!tuple.outputCurrency.equals(currAmt.getCurrency().toString())) {
+ return "Expected currency: " + tuple.outputCurrency + ", got: " + currAmt.getCurrency();
+ }
+ return null;
+ }
+
+ /**
+ * @param tuple
+ * @return
+ */
+ private DecimalFormat newDecimalFormat(NumberFormatTestData tuple) {
+
+ DecimalFormat fmt = new DecimalFormat(
+ tuple.pattern == null ? "0" : tuple.pattern,
+ new DecimalFormatSymbols(tuple.locale == null ? EN : tuple.locale));
+ adjustDecimalFormat(tuple, fmt);
+ return fmt;
+ }
+ /**
+ * @param tuple
+ * @param fmt
+ */
+ private void adjustDecimalFormat(NumberFormatTestData tuple, DecimalFormat fmt) {
+ if (tuple.minIntegerDigits != null) {
+ fmt.setMinimumIntegerDigits(tuple.minIntegerDigits);
+ }
+ if (tuple.maxIntegerDigits != null) {
+ fmt.setMaximumIntegerDigits(tuple.maxIntegerDigits);
+ }
+ if (tuple.minFractionDigits != null) {
+ fmt.setMinimumFractionDigits(tuple.minFractionDigits);
+ }
+ if (tuple.maxFractionDigits != null) {
+ fmt.setMaximumFractionDigits(tuple.maxFractionDigits);
+ }
+ if (tuple.currency != null) {
+ fmt.setCurrency(tuple.currency);
+ }
+ if (tuple.minGroupingDigits != null) {
+ // Oops we don't support this.
+ }
+ if (tuple.useSigDigits != null) {
+ fmt.setSignificantDigitsUsed(
+ tuple.useSigDigits != 0);
+ }
+ if (tuple.minSigDigits != null) {
+ fmt.setMinimumSignificantDigits(tuple.minSigDigits);
+ }
+ if (tuple.maxSigDigits != null) {
+ fmt.setMaximumSignificantDigits(tuple.maxSigDigits);
+ }
+ if (tuple.useGrouping != null) {
+ fmt.setGroupingUsed(tuple.useGrouping != 0);
+ }
+ if (tuple.multiplier != null) {
+ fmt.setMultiplier(tuple.multiplier);
+ }
+ if (tuple.roundingIncrement != null) {
+ fmt.setRoundingIncrement(tuple.roundingIncrement.doubleValue());
+ }
+ if (tuple.formatWidth != null) {
+ fmt.setFormatWidth(tuple.formatWidth);
+ }
+ if (tuple.padCharacter != null && tuple.padCharacter.length() > 0) {
+ fmt.setPadCharacter(tuple.padCharacter.charAt(0));
+ }
+ if (tuple.useScientific != null) {
+ fmt.setScientificNotation(tuple.useScientific != 0);
+ }
+ if (tuple.grouping != null) {
+ fmt.setGroupingSize(tuple.grouping);
+ }
+ if (tuple.grouping2 != null) {
+ fmt.setSecondaryGroupingSize(tuple.grouping2);
+ }
+ if (tuple.roundingMode != null) {
+ fmt.setRoundingMode(tuple.roundingMode);
+ }
+ if (tuple.currencyUsage != null) {
+ fmt.setCurrencyUsage(tuple.currencyUsage);
+ } if (tuple.minimumExponentDigits != null) {
+ fmt.setMinimumExponentDigits(
+ tuple.minimumExponentDigits.byteValue());
+ }
+ if (tuple.exponentSignAlwaysShown != null) {
+ fmt.setExponentSignAlwaysShown(
+ tuple.exponentSignAlwaysShown != 0);
+ }
+ if (tuple.decimalSeparatorAlwaysShown != null) {
+ fmt.setDecimalSeparatorAlwaysShown(
+ tuple.decimalSeparatorAlwaysShown != 0);
+ }
+ if (tuple.padPosition != null) {
+ fmt.setPadPosition(tuple.padPosition);
+ }
+ if (tuple.positivePrefix != null) {
+ fmt.setPositivePrefix(tuple.positivePrefix);
+ }
+ if (tuple.positiveSuffix != null) {
+ fmt.setPositiveSuffix(tuple.positiveSuffix);
+ }
+ if (tuple.negativePrefix != null) {
+ fmt.setNegativePrefix(tuple.negativePrefix);
+ }
+ if (tuple.negativeSuffix != null) {
+ fmt.setNegativeSuffix(tuple.negativeSuffix);
+ }
+ if (tuple.localizedPattern != null) {
+ fmt.applyLocalizedPattern(tuple.localizedPattern);
+ }
+ int lenient = tuple.lenient == null ? 1 : tuple.lenient.intValue();
+ fmt.setParseStrict(lenient == 0);
+ if (tuple.parseIntegerOnly != null) {
+ fmt.setParseIntegerOnly(tuple.parseIntegerOnly != 0);
+ }
+ if (tuple.decimalPatternMatchRequired != null) {
+ fmt.setDecimalPatternMatchRequired(tuple.decimalPatternMatchRequired != 0);
+ }
+ if (tuple.parseNoExponent != null) {
+ // Oops, not supported for now
+ }
+ }
+ };
+
+
+ private DataDrivenNumberFormatTestUtility.CodeUnderTest JDK =
+ new DataDrivenNumberFormatTestUtility.CodeUnderTest() {
+ @Override
+ public Character Id() { return 'K'; }
+
+ @Override
+ public String format(NumberFormatTestData tuple) {
+ java.text.DecimalFormat fmt = newDecimalFormat(tuple);
+ String actual = fmt.format(toNumber(tuple.format));
+ String expected = tuple.output;
+ if (!expected.equals(actual)) {
+ return "Expected " + expected + ", got " + actual;
+ }
+ return null;
+ }
+
+ @Override
+ public String toPattern(NumberFormatTestData tuple) {
+ java.text.DecimalFormat fmt = newDecimalFormat(tuple);
+ StringBuilder result = new StringBuilder();
+ if (tuple.toPattern != null) {
+ String expected = tuple.toPattern;
+ String actual = fmt.toPattern();
+ if (!expected.equals(actual)) {
+ result.append("Expected toPattern=" + expected + ", got " + actual);
+ }
+ }
+ if (tuple.toLocalizedPattern != null) {
+ String expected = tuple.toLocalizedPattern;
+ String actual = fmt.toLocalizedPattern();
+ if (!expected.equals(actual)) {
+ result.append("Expected toLocalizedPattern=" + expected + ", got " + actual);
+ }
+ }
+ return result.length() == 0 ? null : result.toString();
+ }
+
+ @Override
+ public String parse(NumberFormatTestData tuple) {
+ java.text.DecimalFormat fmt = newDecimalFormat(tuple);
+ ParsePosition ppos = new ParsePosition(0);
+ Number actual = fmt.parse(tuple.parse, ppos);
+ if (ppos.getIndex() == 0) {
+ if (!tuple.output.equals("fail")) {
+ return "Parse error expected.";
+ }
+ return null;
+ }
+ if (tuple.output.equals("fail")) {
+ return "Parse succeeded: "+actual+", but was expected to fail.";
+ }
+ Number expected = toNumber(tuple.output);
+ // number types cannot be compared, this is the best we can do.
+ if (expected.doubleValue() != actual.doubleValue()) {
+ return "Expected: " + expected + ", got: " + actual;
+ }
+ return null;
+ }
+
+
+
+ /**
+ * @param tuple
+ * @return
+ */
+ private java.text.DecimalFormat newDecimalFormat(NumberFormatTestData tuple) {
+ java.text.DecimalFormat fmt = new java.text.DecimalFormat(
+ tuple.pattern == null ? "0" : tuple.pattern,
+ new java.text.DecimalFormatSymbols(
+ (tuple.locale == null ? EN : tuple.locale).toLocale()));
+ adjustDecimalFormat(tuple, fmt);
+ return fmt;
+ }
+
+ /**
+ * @param tuple
+ * @param fmt
+ */
+ private void adjustDecimalFormat(NumberFormatTestData tuple, java.text.DecimalFormat fmt) {
+ if (tuple.minIntegerDigits != null) {
+ fmt.setMinimumIntegerDigits(tuple.minIntegerDigits);
+ }
+ if (tuple.maxIntegerDigits != null) {
+ fmt.setMaximumIntegerDigits(tuple.maxIntegerDigits);
+ }
+ if (tuple.minFractionDigits != null) {
+ fmt.setMinimumFractionDigits(tuple.minFractionDigits);
+ }
+ if (tuple.maxFractionDigits != null) {
+ fmt.setMaximumFractionDigits(tuple.maxFractionDigits);
+ }
+ if (tuple.currency != null) {
+ fmt.setCurrency(java.util.Currency.getInstance(tuple.currency.toString()));
+ }
+ if (tuple.minGroupingDigits != null) {
+ // Oops we don't support this.
+ }
+ if (tuple.useSigDigits != null) {
+ // Oops we don't support this
+ }
+ if (tuple.minSigDigits != null) {
+ // Oops we don't support this
+ }
+ if (tuple.maxSigDigits != null) {
+ // Oops we don't support this
+ }
+ if (tuple.useGrouping != null) {
+ fmt.setGroupingUsed(tuple.useGrouping != 0);
+ }
+ if (tuple.multiplier != null) {
+ fmt.setMultiplier(tuple.multiplier);
+ }
+ if (tuple.roundingIncrement != null) {
+ // Not supported
+ }
+ if (tuple.formatWidth != null) {
+ // Not supported
+ }
+ if (tuple.padCharacter != null && tuple.padCharacter.length() > 0) {
+ // Not supported
+ }
+ if (tuple.useScientific != null) {
+ // Not supported
+ }
+ if (tuple.grouping != null) {
+ fmt.setGroupingSize(tuple.grouping);
+ }
+ if (tuple.grouping2 != null) {
+ // Not supported
+ }
+ if (tuple.roundingMode != null) {
+ // Not supported
+ }
+ if (tuple.currencyUsage != null) {
+ // Not supported
+ }
+ if (tuple.minimumExponentDigits != null) {
+ // Not supported
+ }
+ if (tuple.exponentSignAlwaysShown != null) {
+ // Not supported
+ }
+ if (tuple.decimalSeparatorAlwaysShown != null) {
+ fmt.setDecimalSeparatorAlwaysShown(
+ tuple.decimalSeparatorAlwaysShown != 0);
+ }
+ if (tuple.padPosition != null) {
+ // Not supported
+ }
+ if (tuple.positivePrefix != null) {
+ fmt.setPositivePrefix(tuple.positivePrefix);
+ }
+ if (tuple.positiveSuffix != null) {
+ fmt.setPositiveSuffix(tuple.positiveSuffix);
+ }
+ if (tuple.negativePrefix != null) {
+ fmt.setNegativePrefix(tuple.negativePrefix);
+ }
+ if (tuple.negativeSuffix != null) {
+ fmt.setNegativeSuffix(tuple.negativeSuffix);
+ }
+ if (tuple.localizedPattern != null) {
+ fmt.applyLocalizedPattern(tuple.localizedPattern);
+ }
+
+ // lenient parsing not supported by JDK
+ if (tuple.parseIntegerOnly != null) {
+ fmt.setParseIntegerOnly(tuple.parseIntegerOnly != 0);
+ }
+ if (tuple.decimalPatternMatchRequired != null) {
+ // Oops, not supported
+ }
+ if (tuple.parseNoExponent != null) {
+ // Oops, not supported for now
+ }
+ }
+ };
+
@Test
public void TestRoundingScientific10542() {
DecimalFormat format =
DecimalFormatSymbols sym = new DecimalFormatSymbols(Locale.US);
final String pat[] = { "#.#", "#.", ".#", "#" };
int pat_length = pat.length;
- final String newpat[] = { "0.#", "0.", "#.0", "0" };
+ final String newpat[] = { "#0.#", "#0.", "#.0", "#" };
final String num[] = { "0", "0.", ".0", "0" };
for (int i=0; i<pat_length; ++i)
{
{"$124", "4", "-1"},
{"$124 $124", "4", "-1"},
{"$124 ", "4", "-1"},
- {"$124 ", "4", "-1"},
{"$ 124 ", "5", "-1"},
{"$\u00A0124 ", "5", "-1"},
- {" $ 124 ", "6", "-1"},
- {"124$", "3", "-1"},
- {"124 $", "3", "-1"},
- {"$124\u200D", "4", "-1"},
- {"$\u200D124", "5", "-1"},
+ {" $ 124 ", "0", "0"}, // TODO: need to handle space correctly
+ {"124$", "0", "3"}, // TODO: need to handle space correctly
+ // {"124 $", "5", "-1"}, TODO: OK or NOT?
+ {"124 $", "0", "3"},
};
NumberFormat foo = NumberFormat.getCurrencyInstance();
for (int i = 0; i < DATA.length; ++i) {
}
}
- @Test
- public void TestSpaceParsingStrict() {
- // All trailing grouping separators should be ignored in strict mode, not just the first.
- Object[][] cases = {
- {"123 ", 3, -1},
- {"123 ", 3, -1},
- {"123 ,", 3, -1},
- {"123,", 3, -1},
- {"123, ", 3, -1},
- {"123,,", 3, -1},
- {"123,, ", 3, -1},
- {"123 ,", 3, -1},
- {"123, ", 3, -1},
- {"123, 456", 3, -1},
- {"123 456", 0, 8} // TODO: Does this behavior make sense?
- };
- DecimalFormat df = new DecimalFormat("#,###");
- df.setParseStrict(true);
- for (Object[] cas : cases) {
- String input = (String) cas[0];
- int expectedIndex = (Integer) cas[1];
- int expectedErrorIndex = (Integer) cas[2];
- ParsePosition ppos = new ParsePosition(0);
- df.parse(input, ppos);
- assertEquals("Failed on index: '" + input + "'", expectedIndex, ppos.getIndex());
- assertEquals("Failed on error: '" + input + "'", expectedErrorIndex, ppos.getErrorIndex());
- }
- }
@Test
public void TestMultiCurrencySign() {
}
try {
// mix style parsing
- for (int k=3; k<=4; ++k) {
+ for (int k=3; k<=5; ++k) {
// DATA[i][3] is the currency format result using a
// single currency sign.
// DATA[i][4] is the currency format result using
// double currency sign.
// DATA[i][5] is the currency format result using
// triple currency sign.
- // ICU 59: long name parsing requires currency mode.
String oneCurrencyFormat = DATA[i][k];
if (fmt.parse(oneCurrencyFormat).doubleValue() !=
numberToBeFormat.doubleValue()) {
if (!strBuf.equals(formatResult)) {
errln("FAIL: localeID: " + localeString + ", expected(" + formatResult.length() + "): \"" + formatResult + "\", actual(" + strBuf.length() + "): \"" + strBuf + "\"");
}
- // test parsing, and test parsing for all currency formats.
- for (int j = 3; j < 6; ++j) {
- // DATA[i][3] is the currency format result using
- // CURRENCYSTYLE formatter.
- // DATA[i][4] is the currency format result using
- // ISOCURRENCYSTYLE formatter.
- // DATA[i][5] is the currency format result using
- // PLURALCURRENCYSTYLE formatter.
- String oneCurrencyFormatResult = DATA[i][j];
- CurrencyAmount val = numFmt.parseCurrency(oneCurrencyFormatResult, null);
- if (val.getNumber().doubleValue() != numberToBeFormat.doubleValue()) {
- errln("FAIL: getCurrencyFormat of locale " + localeString + " failed roundtripping the number. val=" + val + "; expected: " + numberToBeFormat);
+ try {
+ // test parsing, and test parsing for all currency formats.
+ for (int j = 3; j < 6; ++j) {
+ // DATA[i][3] is the currency format result using
+ // CURRENCYSTYLE formatter.
+ // DATA[i][4] is the currency format result using
+ // ISOCURRENCYSTYLE formatter.
+ // DATA[i][5] is the currency format result using
+ // PLURALCURRENCYSTYLE formatter.
+ String oneCurrencyFormatResult = DATA[i][j];
+ Number val = numFmt.parse(oneCurrencyFormatResult);
+ if (val.doubleValue() != numberToBeFormat.doubleValue()) {
+ errln("FAIL: getCurrencyFormat of locale " + localeString + " failed roundtripping the number. val=" + val + "; expected: " + numberToBeFormat);
+ }
}
}
+ catch (ParseException e) {
+ errln("FAIL: " + e.getMessage());
+ }
}
}
}
public void TestMiscCurrencyParsing() {
String[][] DATA = {
// each has: string to be parsed, parsed position, error position
- {"1.00 ", "4", "-1", "0", "5"},
- {"1.00 UAE dirha", "4", "-1", "0", "14"},
- {"1.00 us dollar", "4", "-1", "14", "-1"},
- {"1.00 US DOLLAR", "4", "-1", "14", "-1"},
- {"1.00 usd", "4", "-1", "8", "-1"},
- {"1.00 USD", "4", "-1", "8", "-1"},
+ {"1.00 ", "0", "4"},
+ {"1.00 UAE dirha", "0", "4"},
+ {"1.00 us dollar", "14", "-1"},
+ {"1.00 US DOLLAR", "14", "-1"},
+ {"1.00 usd", "0", "4"},
};
ULocale locale = new ULocale("en_US");
for (int i=0; i<DATA.length; ++i) {
String stringToBeParsed = DATA[i][0];
int parsedPosition = Integer.parseInt(DATA[i][1]);
int errorIndex = Integer.parseInt(DATA[i][2]);
- int currParsedPosition = Integer.parseInt(DATA[i][3]);
- int currErrorIndex = Integer.parseInt(DATA[i][4]);
NumberFormat numFmt = NumberFormat.getInstance(locale, NumberFormat.CURRENCYSTYLE);
ParsePosition parsePosition = new ParsePosition(0);
Number val = numFmt.parse(stringToBeParsed, parsePosition);
if (parsePosition.getIndex() != parsedPosition ||
parsePosition.getErrorIndex() != errorIndex) {
- errln("FAIL: parse failed on case "+i+". expected position: " + parsedPosition +"; actual: " + parsePosition.getIndex());
- errln("FAIL: parse failed on case "+i+". expected error position: " + errorIndex + "; actual: " + parsePosition.getErrorIndex());
+ errln("FAIL: parse failed. expected error position: " + errorIndex + "; actual: " + parsePosition.getErrorIndex());
+ errln("FAIL: parse failed. expected position: " + parsedPosition +"; actual: " + parsePosition.getIndex());
}
if (parsePosition.getErrorIndex() == -1 &&
val.doubleValue() != 1.00) {
errln("FAIL: parse failed. expected 1.00, actual:" + val);
}
- parsePosition = new ParsePosition(0);
- CurrencyAmount amt = numFmt.parseCurrency(stringToBeParsed, parsePosition);
- if (parsePosition.getIndex() != currParsedPosition ||
- parsePosition.getErrorIndex() != currErrorIndex) {
- errln("FAIL: parseCurrency failed on case "+i+". expected error position: " + currErrorIndex + "; actual: " + parsePosition.getErrorIndex());
- errln("FAIL: parseCurrency failed on case "+i+". expected position: " + currParsedPosition +"; actual: " + parsePosition.getIndex());
- }
- if (parsePosition.getErrorIndex() == -1 &&
- amt.getNumber().doubleValue() != 1.00) {
- errln("FAIL: parseCurrency failed. expected 1.00, actual:" + val);
- }
}
}
public int getCurExpectVal() { return curExpectVal; }
public String getCurExpectCurr() { return curExpectCurr; }
}
- // Note: In cases where the number occurs before the currency sign, non-currency mode will parse the number
- // and stop when it reaches the currency symbol.
final ParseCurrencyItem[] parseCurrencyItems = {
new ParseCurrencyItem( "en_US", "dollars2", "$2.00", 5, 2, 5, 2, "USD" ),
new ParseCurrencyItem( "en_US", "dollars4", "$4", 2, 4, 2, 4, "USD" ),
- new ParseCurrencyItem( "en_US", "dollars9", "9\u00A0$", 1, 9, 3, 9, "USD" ),
+ new ParseCurrencyItem( "en_US", "dollars9", "9\u00A0$", 0, 0, 0, 0, "" ),
new ParseCurrencyItem( "en_US", "pounds3", "\u00A33.00", 0, 0, 5, 3, "GBP" ),
new ParseCurrencyItem( "en_US", "pounds5", "\u00A35", 0, 0, 2, 5, "GBP" ),
- new ParseCurrencyItem( "en_US", "pounds7", "7\u00A0\u00A3", 1, 7, 3, 7, "GBP" ),
+ new ParseCurrencyItem( "en_US", "pounds7", "7\u00A0\u00A3", 0, 0, 0, 0, "" ),
new ParseCurrencyItem( "en_US", "euros8", "\u20AC8", 0, 0, 2, 8, "EUR" ),
new ParseCurrencyItem( "en_GB", "pounds3", "\u00A33.00", 5, 3, 5, 3, "GBP" ),
new ParseCurrencyItem( "en_GB", "pounds5", "\u00A35", 2, 5, 2, 5, "GBP" ),
- new ParseCurrencyItem( "en_GB", "pounds7", "7\u00A0\u00A3", 1, 7, 3, 7, "GBP" ),
- new ParseCurrencyItem( "en_GB", "euros4", "4,00\u00A0\u20AC", 4,400, 6,400, "EUR" ),
- new ParseCurrencyItem( "en_GB", "euros6", "6\u00A0\u20AC", 1, 6, 3, 6, "EUR" ),
+ new ParseCurrencyItem( "en_GB", "pounds7", "7\u00A0\u00A3", 0, 0, 0, 0, "" ),
+ new ParseCurrencyItem( "en_GB", "euros4", "4,00\u00A0\u20AC", 0, 0, 0, 0, "" ),
+ new ParseCurrencyItem( "en_GB", "euros6", "6\u00A0\u20AC", 0, 0, 0, 0, "" ),
new ParseCurrencyItem( "en_GB", "euros8", "\u20AC8", 0, 0, 2, 8, "EUR" ),
new ParseCurrencyItem( "en_GB", "dollars4", "US$4", 0, 0, 4, 4, "USD" ),
new ParseCurrencyItem( "fr_FR", "euros4", "4,00\u00A0\u20AC", 6, 4, 6, 4, "EUR" ),
new ParseCurrencyItem( "fr_FR", "euros6", "6\u00A0\u20AC", 3, 6, 3, 6, "EUR" ),
- new ParseCurrencyItem( "fr_FR", "euros8", "\u20AC8", 0, 0, 2, 8, "EUR" ),
+ new ParseCurrencyItem( "fr_FR", "euros8", "\u20AC8", 0, 0, 0, 0, "" ),
new ParseCurrencyItem( "fr_FR", "dollars2", "$2.00", 0, 0, 0, 0, "" ),
new ParseCurrencyItem( "fr_FR", "dollars4", "$4", 0, 0, 0, 0, "" ),
};
}
}
- @Test
- public void TestParseCurrencyWithWhitespace() {
- DecimalFormat df = new DecimalFormat("#,##0.00 ¤¤");
- ParsePosition ppos = new ParsePosition(0);
- df.parseCurrency("1.00 us denmark", ppos);
- assertEquals("Expected to fail on 'us denmark' string", 9, ppos.getErrorIndex());
- }
-
@Test
public void TestParseCurrPatternWithDecStyle() {
String currpat = "¤#,##0.00";
DecimalFormat f = new DecimalFormat("#,##,###", US);
expect(f, 123456789L, "12,34,56,789");
- expectPat(f, "#,##,##0");
+ expectPat(f, "#,##,###");
f.applyPattern("#,###");
f.setSecondaryGroupingSize(4);
expect(f, 123456789L, "12,3456,789");
- expectPat(f, "#,####,##0");
+ expectPat(f, "#,####,###");
NumberFormat g = NumberFormat.getInstance(new Locale("hi", "IN"));
String out = "";
errln("FAIL Pattern rt \"" + pat + "\" . \"" + pat2 + "\"");
}
// Make sure digit counts match what we expect
- if (i == 0) continue; // outputs to 1,1,0,0 since at least one min digit is required.
if (df.getMinimumIntegerDigits() != DIGITS[4 * i]
|| df.getMaximumIntegerDigits() != DIGITS[4 * i + 1]
|| df.getMinimumFractionDigits() != DIGITS[4 * i + 2]
UnicodeString pattern(patternChars);
expectPad(fmt, pattern , DecimalFormat.kPadBeforePrefix, 4, padString);
*/
-
- // Test multi-char padding sequence specified via pattern
- expect2(new DecimalFormat("*'😃'####.00", US), 1.1, "😃😃😃1.10");
}
/**
// 12 3456789012345
expectPat(fmt, "AA*^####,##0.00ZZ"); // This is the interesting case
- // The new implementation produces "AA*^#####,##0.00ZZ", which is functionally equivalent
- // to what the old implementation produced, "AA*^#,###,##0.00ZZ"
fmt.setFormatWidth(16);
// 12 34567890123456
- //expectPat(fmt, "AA*^#,###,##0.00ZZ");
- expectPat(fmt, "AA*^#####,##0.00ZZ");
+ expectPat(fmt, "AA*^#,###,##0.00ZZ");
}
@Test
errln("FAIL: isScientificNotation = true, expect false");
}
- // Create a new instance to flush out currency info
- df = new DecimalFormat("0.00000", US);
+ df.applyPattern("0.00000");
df.setScientificNotation(true);
if (!df.isScientificNotation()) {
errln("FAIL: isScientificNotation = false, expect true");
}
}
- @Test
- public void TestParseNull() throws ParseException {
- DecimalFormat df = new DecimalFormat();
- try {
- df.parse(null);
- fail("df.parse(null) didn't throw an exception");
- } catch (IllegalArgumentException e){}
- try {
- df.parse(null, null);
- fail("df.parse(null) didn't throw an exception");
- } catch (IllegalArgumentException e){}
- try {
- df.parseCurrency(null, null);
- fail("df.parse(null) didn't throw an exception");
- } catch (IllegalArgumentException e){}
- }
-
@Test
public void TestWhiteSpaceParsing() {
DecimalFormatSymbols US = new DecimalFormatSymbols(Locale.US);
int n = 1234;
expect(fmt, "a b1234c ", n);
expect(fmt, "a b1234c ", n);
- expect(fmt, "ab1234", n);
-
- fmt.applyPattern("a b #");
- expect(fmt, "ab1234", n);
- expect(fmt, "ab 1234", n);
- expect(fmt, "a b1234", n);
- expect(fmt, "a b1234", n);
}
/**
// Support methods
//------------------------------------------------------------------
- /** Format-Parse test */
+ // Format-Parse test
public void expect2(NumberFormat fmt, Number n, String exp) {
// Don't round-trip format test, since we explicitly do it
expect(fmt, n, exp, false);
expect(fmt, exp, n);
}
- /** Format-Parse test */
+ // Format-Parse test
public void expect3(NumberFormat fmt, Number n, String exp) {
// Don't round-trip format test, since we explicitly do it
expect_rbnf(fmt, n, exp, false);
expect_rbnf(fmt, exp, n);
}
- /** Format-Parse test (convenience) */
+ // Format-Parse test (convenience)
public void expect2(NumberFormat fmt, double n, String exp) {
expect2(fmt, new Double(n), exp);
}
- /** RBNF Format-Parse test (convenience) */
+ // Format-Parse test (convenience)
public void expect3(NumberFormat fmt, double n, String exp) {
expect3(fmt, new Double(n), exp);
}
- /** Format-Parse test (convenience) */
+ // Format-Parse test (convenience)
public void expect2(NumberFormat fmt, long n, String exp) {
expect2(fmt, new Long(n), exp);
}
- /** RBNF Format-Parse test (convenience) */
+ // Format-Parse test (convenience)
public void expect3(NumberFormat fmt, long n, String exp) {
expect3(fmt, new Long(n), exp);
}
- /** Format test */
+ // Format test
public void expect(NumberFormat fmt, Number n, String exp, boolean rt) {
StringBuffer saw = new StringBuffer();
FieldPosition pos = new FieldPosition(0);
", FAIL " + n + " x " + pat + " = \"" + saw + "\", expected \"" + exp + "\"");
}
}
- /** RBNF format test */
+ // Format test
public void expect_rbnf(NumberFormat fmt, Number n, String exp, boolean rt) {
StringBuffer saw = new StringBuffer();
FieldPosition pos = new FieldPosition(0);
}
}
- /** Format test (convenience) */
+ // Format test (convenience)
public void expect(NumberFormat fmt, Number n, String exp) {
expect(fmt, n, exp, true);
}
- /** Format test (convenience) */
+ // Format test (convenience)
public void expect(NumberFormat fmt, double n, String exp) {
expect(fmt, new Double(n), exp);
}
- /** Format test (convenience) */
+ // Format test (convenience)
public void expect(NumberFormat fmt, long n, String exp) {
expect(fmt, new Long(n), exp);
}
- /** Parse test */
+ // Parse test
public void expect(NumberFormat fmt, String str, Number n) {
Number num = null;
try {
}
}
- /** RBNF Parse test */
+ // Parse test
public void expect_rbnf(NumberFormat fmt, String str, Number n) {
Number num = null;
try {
}
}
- /** Parse test (convenience) */
+ // Parse test (convenience)
public void expect(NumberFormat fmt, String str, double n) {
expect(fmt, str, new Double(n));
}
- /** Parse test (convenience) */
+ // Parse test (convenience)
public void expect(NumberFormat fmt, String str, long n) {
expect(fmt, str, new Long(n));
}
}
}
- @Test
- public void TestScientificWithGrouping() {
- DecimalFormat df = new DecimalFormat("#,##0.000E0");
- expect2(df, 123, "123.0E0");
- expect2(df, 1234, "1,234E0");
- expect2(df, 12340, "1.234E4");
- }
-
@Test
public void TestStrictParse() {
String[] pass = {
"00", // leading zero before zero - used to be error - see ticket #7913
"012", // leading zero before digit - used to be error - see ticket #7913
"0,456", // leading zero before group separator - used to be error - see ticket #7913
- "999,999", // see ticket #6863
- "-99,999", // see ticket #6863
- "-999,999", // see ticket #6863
- "-9,999,999", // see ticket #6863
};
String[] fail = {
"1,2", // wrong number of digits after group separator
"00E2", // leading zeroes now allowed in strict mode - see ticket #
};
String[] scientificFail = {
+ "1,234E2", // group separators with exponent fail
};
nf = (DecimalFormat) NumberFormat.getInstance(Locale.ENGLISH);
// For valid array, it is displayed as {min value, max value}
// Tests when "if (minimumIntegerDigits > maximumIntegerDigits)" is true
int[][] cases = { { -1, 0 }, { 0, 1 }, { 1, 0 }, { 2, 0 }, { 2, 1 }, { 10, 0 } };
- int[] expectedMax = { 1, 1, 1, 2, 2, 10 };
+ int[] expectedMax = { 0, 1, 1, 2, 2, 10 };
if (cases.length != expectedMax.length) {
errln("Can't continue test case method TestSetMinimumIntegerDigits "
+ "since the test case arrays are unequal.");
} else {
for (int i = 0; i < cases.length; i++) {
- nf.setMinimumIntegerDigits(cases[i][0]);
nf.setMaximumIntegerDigits(cases[i][1]);
+ nf.setMinimumIntegerDigits(cases[i][0]);
if (nf.getMaximumIntegerDigits() != expectedMax[i]) {
errln("NumberFormat.setMinimumIntegerDigits(int newValue "
- + "did not return an expected result for parameter " + cases[i][0] + " and " + cases[i][1]
+ + "did not return an expected result for parameter " + cases[i][1] + " and " + cases[i][0]
+ " and expected " + expectedMax[i] + " but got " + nf.getMaximumIntegerDigits());
}
}
}
}
- /*
- * This feature had to do with a limitation in DigitList.java that no longer exists in the
- * new implementation.
- *
@Test
public void TestParseMaxDigits() {
DecimalFormat fmt = new DecimalFormat();
fmt.setParseMaxDigits(-1);
- // Default value is 1000
+ /* Default value is 1000 */
if (fmt.getParseMaxDigits() != 1000) {
errln("Fail valid value checking in setParseMaxDigits.");
}
}
}
- */
private static class FormatCharItrTestThread implements Runnable {
private final NumberFormat fmt;
}
}
- @Test
- public void TestCurrencyWithMinMaxFractionDigits() {
- DecimalFormat df = new DecimalFormat();
- df.applyPattern("¤#,##0.00");
- df.setCurrency(Currency.getInstance("USD"));
- assertEquals("Basic currency format fails", "$1.23", df.format(1.234));
- df.setMaximumFractionDigits(4);
- assertEquals("Currency with max fraction == 4", "$1.234", df.format(1.234));
- df.setMinimumFractionDigits(4);
- assertEquals("Currency with min fraction == 4", "$1.2340", df.format(1.234));
- }
-
@Test
public void TestParseRequiredDecimalPoint() {
String[] testPattern = { "00.####", "00.0", "00" };
String value2Parse = "99";
- String value2ParseWithDecimal = "99.9";
double parseValue = 99;
- double parseValueWithDecimal = 99.9;
DecimalFormat parser = new DecimalFormat();
double result;
boolean hasDecimalPoint;
TestFmwk.errln("Parsing " + value2Parse + " should have succeeded with " + testPattern[i] +
" and isDecimalPointMatchRequired set to: " + parser.isDecimalPatternMatchRequired());
}
- try {
- result = parser.parse(value2ParseWithDecimal).doubleValue();
- assertEquals("wrong parsed value", parseValueWithDecimal, result);
- } catch (ParseException e) {
- TestFmwk.errln("Parsing " + value2ParseWithDecimal + " should have succeeded with " + testPattern[i] +
- " and isDecimalPointMatchRequired set to: " + parser.isDecimalPatternMatchRequired());
- }
parser.setDecimalPatternMatchRequired(true);
try {
} catch (ParseException e) {
// OK, should fail
}
- try {
- result = parser.parse(value2ParseWithDecimal).doubleValue();
- if(!hasDecimalPoint){
- TestFmwk.errln("Parsing " + value2ParseWithDecimal + " should NOT have succeeded with " + testPattern[i] +
- " and isDecimalPointMatchRequired set to: " + parser.isDecimalPatternMatchRequired());
- }
- } catch (ParseException e) {
- // OK, should fail
- }
}
+
+ }
+
+
+ //TODO(junit): investigate
+ @Test
+ public void TestDataDrivenICU() {
+ DataDrivenNumberFormatTestUtility.runSuite(
+ "numberformattestspecification.txt", ICU);
}
+ //TODO(junit): investigate
+ @Test
+ public void TestDataDrivenJDK() {
+ DataDrivenNumberFormatTestUtility.runSuite(
+ "numberformattestspecification.txt", JDK);
+ }
+
+
@Test
public void TestCurrFmtNegSameAsPositive() {
DecimalFormatSymbols decfmtsym = DecimalFormatSymbols.getInstance(Locale.US);
decfmtsym.setMinusSign('\u200B'); // ZERO WIDTH SPACE, in ICU4J cannot set to empty string
- DecimalFormat decfmt = new DecimalFormat("\u00A4#,##0.00;-\u00A4#,##0.00", decfmtsym);
+ DecimalFormat decfmt = new DecimalFormat("\u00A4#,##0.00;\u00A4#,##0.00", decfmtsym);
String currFmtResult = decfmt.format(-100.0);
if (!currFmtResult.equals("\u200B$100.00")) {
errln("decfmt.toPattern results wrong, expected \u200B$100.00, got " + currFmtResult);
@Test
public void TestNumberFormatTestDataToString() {
- new DataDrivenNumberFormatTestData().toString();
+ new NumberFormatTestData().toString();
}
// Testing for Issue 11805.
result.get(i).value);
}
}
- assertTrue("Comparing vector results for " + formattedOutput, expected.containsAll(result));
+ // TODO: restore when #11914 is fixed.
+ // assertTrue("Comparing vector results for " + formattedOutput,
+ // expected.containsAll(result));
}
// Testing for Issue 11914, missing FieldPositions for some field types.
public void TestStringSymbols() {
DecimalFormatSymbols symbols = new DecimalFormatSymbols(ULocale.US);
- // Attempt digits with multiple code points.
String[] customDigits = {"(0)", "(1)", "(2)", "(3)", "(4)", "(5)", "(6)", "(7)", "(8)", "(9)"};
symbols.setDigitStrings(customDigits);
- DecimalFormat fmt = new DecimalFormat("#,##0.0#", symbols);
- expect2(fmt, 1234567.89, "(1),(2)(3)(4),(5)(6)(7).(8)(9)");
-
- // Scientific notation should work.
- fmt.applyPattern("@@@E0");
- expect2(fmt, 1230000, "(1).(2)(3)E(6)");
-
- // Grouping and decimal with multiple code points are not supported during parsing.
symbols.setDecimalSeparatorString("~~");
symbols.setGroupingSeparatorString("^^");
- fmt.setDecimalFormatSymbols(symbols);
- fmt.applyPattern("#,##0.0#");
- assertEquals("Custom decimal and grouping separator string with multiple characters",
- fmt.format(1234567.89), "(1)^^(2)(3)(4)^^(5)(6)(7)~~(8)(9)");
-
- // Digits starting at U+1D7CE MATHEMATICAL BOLD DIGIT ZERO
- // These are all single code points, so parsing will work.
- for (int i=0; i<10; i++) customDigits[i] = new String(Character.toChars(0x1D7CE+i));
- symbols.setDigitStrings(customDigits);
- symbols.setDecimalSeparatorString("😁");
- symbols.setGroupingSeparatorString("😎");
- fmt.setDecimalFormatSymbols(symbols);
- expect2(fmt, 1234.56, "𝟏😎𝟐𝟑𝟒😁𝟓𝟔");
+
+ DecimalFormat fmt = new DecimalFormat("#,##0.0#", symbols);
+
+ expect2(fmt, 1234567.89, "(1)^^(2)(3)(4)^^(5)(6)(7)~~(8)(9)");
}
@Test
public void TestArabicCurrencyPatternInfo() {
ULocale arLocale = new ULocale("ar");
-
+
DecimalFormatSymbols symbols = new DecimalFormatSymbols(arLocale);
String currSpacingPatn = symbols.getPatternForCurrencySpacing(DecimalFormatSymbols.CURRENCY_SPC_CURRENCY_MATCH, true);
if (currSpacingPatn==null || currSpacingPatn.length() == 0) {
errln("locale ar, getPatternForCurrencySpacing returns null or 0-length string");
}
-
+
DecimalFormat currAcctFormat = (DecimalFormat)NumberFormat.getInstance(arLocale, NumberFormat.ACCOUNTINGCURRENCYSTYLE);
String currAcctPatn = currAcctFormat.toPattern();
if (currAcctPatn==null || currAcctPatn.length() == 0) {
errln("locale ar, toPattern for ACCOUNTINGCURRENCYSTYLE returns null or 0-length string");
}
}
-
- @Test
- public void Test10436() {
- DecimalFormat df = (DecimalFormat) NumberFormat.getInstance(Locale.ENGLISH);
- df.setRoundingMode(MathContext.ROUND_CEILING);
- df.setMinimumFractionDigits(0);
- df.setMaximumFractionDigits(0);
- assertEquals("-.99 should round toward infinity", "-0", df.format(-0.99));
- }
-
- @Test
- public void Test10765() {
- NumberFormat fmt = NumberFormat.getInstance(new ULocale("en"));
- fmt.setMinimumIntegerDigits(10);
- FieldPosition pos = new FieldPosition(NumberFormat.Field.GROUPING_SEPARATOR);
- fmt.format(1234, new StringBuffer(), pos);
- assertEquals("FieldPosition should report the first occurence", 1, pos.getBeginIndex());
- assertEquals("FieldPosition should report the first occurence", 2, pos.getEndIndex());
- }
-
- @Test
- public void Test10997() {
- NumberFormat fmt = NumberFormat.getCurrencyInstance(new ULocale("en-US"));
- fmt.setMinimumFractionDigits(4);
- fmt.setMaximumFractionDigits(4);
- String str1 = fmt.format(new CurrencyAmount(123.45, Currency.getInstance("USD")));
- String str2 = fmt.format(new CurrencyAmount(123.45, Currency.getInstance("EUR")));
- assertEquals("minFrac 4 should be respected in default currency", "$123.4500", str1);
- assertEquals("minFrac 4 should be respected in different currency", "€123.4500", str2);
- }
-
- @Test
- public void Test11020() {
- DecimalFormatSymbols sym = new DecimalFormatSymbols(ULocale.FRANCE);
- DecimalFormat fmt = new DecimalFormat("0.05E0", sym);
- String result = fmt.format(12301.2).replace('\u00a0', ' ');
- assertEquals("Rounding increment should be applied after magnitude scaling", "1,25E4", result);
- }
-
- @Test
- public void Test11025() {
- String pattern = "¤¤ **####0.00";
- DecimalFormatSymbols sym = new DecimalFormatSymbols(ULocale.FRANCE);
- DecimalFormat fmt = new DecimalFormat(pattern, sym);
- String result = fmt.format(433.0);
- assertEquals("Number should be padded to 11 characters", "EUR *433,00", result);
- }
-
- @Test
- public void Test11640() {
- DecimalFormat df = (DecimalFormat) NumberFormat.getInstance();
- df.applyPattern("¤¤¤ 0");
- String result = df.getPositivePrefix();
- assertEquals("Triple-currency should give long name on getPositivePrefix", "US dollars ", result);
- }
-
- @Test
- public void Test11645() {
- String pattern = "#,##0.0#";
- DecimalFormat fmt = (DecimalFormat) NumberFormat.getInstance();
- fmt.applyPattern(pattern);
- DecimalFormat fmtCopy;
-
- final int newMultiplier = 37;
- fmtCopy = (DecimalFormat) fmt.clone();
- assertNotEquals("Value before setter", fmtCopy.getMultiplier(), newMultiplier);
- fmtCopy.setMultiplier(newMultiplier);
- assertEquals("Value after setter", fmtCopy.getMultiplier(), newMultiplier);
- fmtCopy.applyPattern(pattern);
- assertEquals("Value after applyPattern", fmtCopy.getMultiplier(), newMultiplier);
- assertFalse("multiplier", fmt.equals(fmtCopy));
-
- final int newRoundingMode = RoundingMode.CEILING.ordinal();
- fmtCopy = (DecimalFormat) fmt.clone();
- assertNotEquals("Value before setter", fmtCopy.getRoundingMode(), newRoundingMode);
- fmtCopy.setRoundingMode(newRoundingMode);
- assertEquals("Value after setter", fmtCopy.getRoundingMode(), newRoundingMode);
- fmtCopy.applyPattern(pattern);
- assertEquals("Value after applyPattern", fmtCopy.getRoundingMode(), newRoundingMode);
- assertFalse("roundingMode", fmt.equals(fmtCopy));
-
- final Currency newCurrency = Currency.getInstance("EAT");
- fmtCopy = (DecimalFormat) fmt.clone();
- assertNotEquals("Value before setter", fmtCopy.getCurrency(), newCurrency);
- fmtCopy.setCurrency(newCurrency);
- assertEquals("Value after setter", fmtCopy.getCurrency(), newCurrency);
- fmtCopy.applyPattern(pattern);
- assertEquals("Value after applyPattern", fmtCopy.getCurrency(), newCurrency);
- assertFalse("currency", fmt.equals(fmtCopy));
-
- final CurrencyUsage newCurrencyUsage = CurrencyUsage.CASH;
- fmtCopy = (DecimalFormat) fmt.clone();
- assertNotEquals("Value before setter", fmtCopy.getCurrencyUsage(), newCurrencyUsage);
- fmtCopy.setCurrencyUsage(CurrencyUsage.CASH);
- assertEquals("Value after setter", fmtCopy.getCurrencyUsage(), newCurrencyUsage);
- fmtCopy.applyPattern(pattern);
- assertEquals("Value after applyPattern", fmtCopy.getCurrencyUsage(), newCurrencyUsage);
- assertFalse("currencyUsage", fmt.equals(fmtCopy));
- }
-
- @Test
- public void Test11646() {
- DecimalFormatSymbols symbols = new DecimalFormatSymbols(new ULocale("en_US"));
- String pattern = "\u00a4\u00a4\u00a4 0.00 %\u00a4\u00a4";
- DecimalFormat fmt = new DecimalFormat(pattern, symbols);
-
- // Test equality with affixes. set affix methods can't capture special
- // characters which is why equality should fail.
- {
- DecimalFormat fmtCopy = (DecimalFormat) fmt.clone();
- assertEquals("", fmt, fmtCopy);
- fmtCopy.setPositivePrefix(fmtCopy.getPositivePrefix());
- assertNotEquals("", fmt, fmtCopy);
- }
- {
- DecimalFormat fmtCopy = (DecimalFormat) fmt.clone();
- assertEquals("", fmt, fmtCopy);
- fmtCopy.setPositiveSuffix(fmtCopy.getPositiveSuffix());
- assertNotEquals("", fmt, fmtCopy);
- }
- {
- DecimalFormat fmtCopy = (DecimalFormat) fmt.clone();
- assertEquals("", fmt, fmtCopy);
- fmtCopy.setNegativePrefix(fmtCopy.getNegativePrefix());
- assertNotEquals("", fmt, fmtCopy);
- }
- {
- DecimalFormat fmtCopy = (DecimalFormat) fmt.clone();
- assertEquals("", fmt, fmtCopy);
- fmtCopy.setNegativeSuffix(fmtCopy.getNegativeSuffix());
- assertNotEquals("", fmt, fmtCopy);
- }
- }
-
- @Test
- public void Test11648() {
- DecimalFormat df = new DecimalFormat("0.00");
- df.setScientificNotation(true);
- String pat = df.toPattern();
- assertEquals("A valid scientific notation pattern should be produced", "0.00E0", pat);
- }
-
- @Test
- public void Test11649() {
- String pattern = "\u00a4\u00a4\u00a4 0.00";
- DecimalFormat fmt = new DecimalFormat(pattern);
- fmt.setCurrency(Currency.getInstance("USD"));
- assertEquals("Triple currency sign should format long name", "US dollars 12.34", fmt.format(12.34));
-
- String newPattern = fmt.toPattern();
- assertEquals("Should produce a valid pattern", pattern, newPattern);
-
- DecimalFormat fmt2 = new DecimalFormat(newPattern);
- fmt2.setCurrency(Currency.getInstance("USD"));
- assertEquals("Triple currency sign pattern should round-trip", "US dollars 12.34", fmt2.format(12.34));
- }
-
- @Test
- public void Test11686() {
- DecimalFormat df = new DecimalFormat();
- df.setPositiveSuffix("0K");
- df.setNegativeSuffix("0N");
- expect2(df, 123, "1230K");
- expect2(df, -123, "1230N");
- }
-
- @Test
- public void Test11839() {
- DecimalFormatSymbols dfs = new DecimalFormatSymbols(ULocale.ENGLISH);
- dfs.setMinusSign('∸');
- dfs.setPlusSign('∔'); // ∔ U+2214 DOT PLUS
- DecimalFormat df = new DecimalFormat("0.00+;0.00-", dfs);
- String result = df.format(-1.234);
- assertEquals("Locale-specific minus sign should be used", "1.23∸", result);
- result = df.format(1.234);
- assertEquals("Locale-specific plus sign should be used", "1.23∔", result);
- }
-
- @Test
- public void Test12753() {
- ULocale locale = new ULocale("en-US");
- DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(locale);
- symbols.setDecimalSeparator('*');
- DecimalFormat df = new DecimalFormat("0.00", symbols);
- df.setDecimalPatternMatchRequired(true);
- try {
- df.parse("123");
- fail("Parsing integer succeeded even though setDecimalPatternMatchRequired was set");
- } catch (ParseException e) {
- // Parse failed (expected)
- }
- }
-
- @Test
- public void Test12962() {
- String pat = "**0.00";
- DecimalFormat df = new DecimalFormat(pat);
- String newPat = df.toPattern();
- assertEquals("Format width changed upon calling applyPattern", pat.length(), newPat.length());
- }
-
- @Test
- public void Test10354() {
- DecimalFormatSymbols dfs = new DecimalFormatSymbols();
- dfs.setNaN("");
- DecimalFormat df = new DecimalFormat();
- df.setDecimalFormatSymbols(dfs);
- try {
- df.formatToCharacterIterator(Double.NaN);
- // pass
- } catch (IllegalArgumentException e) {
- throw new AssertionError(e);
- }
- }
-
- @Test
- public void Test11913() {
- NumberFormat df = DecimalFormat.getInstance();
- String result = df.format(new BigDecimal("1.23456789E400"));
- assertEquals("Should format more than 309 digits", "12,345,678", result.substring(0, 10));
- assertEquals("Should format more than 309 digits", 534, result.length());
- }
-
- @Test
- public void Test12045() {
- if (logKnownIssue("12045", "XSU is missing from fr")) { return; }
-
- NumberFormat nf = NumberFormat.getInstance(new ULocale("fr"), NumberFormat.PLURALCURRENCYSTYLE);
- ParsePosition ppos = new ParsePosition(0);
- try {
- CurrencyAmount result = nf.parseCurrency("2,34 XSU", ppos);
- assertEquals("Parsing should succeed on XSU",
- new CurrencyAmount(2.34, Currency.getInstance("XSU")), result);
- // pass
- } catch (Exception e) {
- throw new AssertionError("Should have been able to parse XSU", e);
- }
- }
-
- @Test
- public void Test11739() {
- NumberFormat nf = NumberFormat.getCurrencyInstance(new ULocale("sr_BA"));
- ((DecimalFormat) nf).applyPattern("0.0 ¤¤¤");
- ParsePosition ppos = new ParsePosition(0);
- CurrencyAmount result = nf.parseCurrency("1.500 амерички долар", ppos);
- assertEquals("Should parse to 1500 USD", new CurrencyAmount(1500, Currency.getInstance("USD")), result);
- }
-
- @Test
- public void Test11647() {
- DecimalFormat df = new DecimalFormat();
- df.applyPattern("¤¤¤¤#");
- String actual = df.format(123);
- assertEquals("Should replace 4 currency signs with U+FFFD", "\uFFFD123", actual);
- }
-
- @Test
- public void Test12567() {
- DecimalFormat df1 = (DecimalFormat) NumberFormat.getInstance(NumberFormat.PLURALCURRENCYSTYLE);
- DecimalFormat df2 = (DecimalFormat) NumberFormat.getInstance(NumberFormat.NUMBERSTYLE);
- df2.setCurrency(df1.getCurrency());
- df2.setCurrencyPluralInfo(df1.getCurrencyPluralInfo());
- df1.applyPattern("0.00");
- df2.applyPattern("0.00");
- assertEquals("df1 == df2", df1, df2);
- assertEquals("df2 == df1", df2, df1);
- df2.setPositivePrefix("abc");
- assertNotEquals("df1 != df2", df1, df2);
- assertNotEquals("df2 != df1", df2, df1);
- }
-
- @Test
- public void testPercentZero() {
- DecimalFormat df = (DecimalFormat) NumberFormat.getPercentInstance();
- String actual = df.format(0);
- assertEquals("Should have one zero digit", "0%", actual);
- }
-
- @Test
- public void testCurrencyZeroRounding() {
- DecimalFormat df = (DecimalFormat) NumberFormat.getCurrencyInstance();
- df.setMaximumFractionDigits(0);
- String actual = df.format(0);
- assertEquals("Should have zero fraction digits", "$0", actual);
- }
-
- @Test
- public void testCustomCurrencySymbol() {
- DecimalFormat df = (DecimalFormat) NumberFormat.getCurrencyInstance();
- df.setCurrency(Currency.getInstance("USD"));
- DecimalFormatSymbols symbols = df.getDecimalFormatSymbols();
- symbols.setCurrencySymbol("#");
- df.setDecimalFormatSymbols(symbols);
- String actual = df.format(123);
- assertEquals("Should use '#' instad of '$'", "#123.00", actual);
- }
-
- @Test
- public void TestBasicSerializationRoundTrip() throws IOException, ClassNotFoundException {
- DecimalFormat df0 = new DecimalFormat("A-**#####,#00.00b¤");
-
- // Write to byte stream
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- ObjectOutputStream oos = new ObjectOutputStream(baos);
- oos.writeObject(df0);
- oos.flush();
- baos.close();
- byte[] bytes = baos.toByteArray();
-
- // Read from byte stream
- ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
- Object obj = ois.readObject();
- ois.close();
- DecimalFormat df1 = (DecimalFormat) obj;
-
- // Test equality
- assertEquals("Did not round-trip through serialization", df0, df1);
-
- // Test basic functionality
- String str0 = df0.format(12345.67);
- String str1 = df1.format(12345.67);
- assertEquals("Serialized formatter does not produce same output", str0, str1);
- }
-
- @Test
- public void testGetSetCurrency() {
- DecimalFormat df = new DecimalFormat("¤#");
- assertEquals("Currency should start out null", null, df.getCurrency());
- Currency curr = Currency.getInstance("EUR");
- df.setCurrency(curr);
- assertEquals("Currency should equal EUR after set", curr, df.getCurrency());
- String result = df.format(123);
- assertEquals("Currency should format as expected in EUR", "€123.00", result);
- }
-
- @Test
- public void testRoundingModeSetters() {
- DecimalFormat df1 = new DecimalFormat();
- DecimalFormat df2 = new DecimalFormat();
-
- df1.setRoundingMode(java.math.BigDecimal.ROUND_CEILING);
- assertNotEquals("Rounding mode was set to a non-default", df1, df2);
- df2.setRoundingMode(com.ibm.icu.math.BigDecimal.ROUND_CEILING);
- assertEquals("Rounding mode from icu.math and java.math should be the same", df1, df2);
- df2.setRoundingMode(java.math.RoundingMode.CEILING.ordinal());
- assertEquals("Rounding mode ordinal from java.math.RoundingMode should be the same", df1, df2);
- }
-
- @Test
- public void testSignificantDigitsMode() {
- String[][] allExpected = {
- {"12340.0", "12340.0", "12340.0"},
- {"1234.0", "1234.0", "1234.0"},
- {"123.4", "123.4", "123.4"},
- {"12.34", "12.34", "12.34"},
- {"1.234", "1.23", "1.23"},
- {"0.1234", "0.12", "0.123"},
- {"0.01234", "0.01", "0.0123"},
- {"0.001234", "0.00", "0.00123"}
- };
-
- DecimalFormat df = new DecimalFormat();
- df.setMinimumFractionDigits(1);
- df.setMaximumFractionDigits(2);
- df.setMinimumSignificantDigits(3);
- df.setMaximumSignificantDigits(4);
- df.setGroupingUsed(false);
-
- SignificantDigitsMode[] modes = new SignificantDigitsMode[] {
- SignificantDigitsMode.OVERRIDE_MAXIMUM_FRACTION,
- SignificantDigitsMode.RESPECT_MAXIMUM_FRACTION,
- SignificantDigitsMode.ENSURE_MINIMUM_SIGNIFICANT
- };
-
- for (double d = 12340.0, i=0; d > 0.001; d /= 10, i++) {
- for (int j=0; j<modes.length; j++) {
- SignificantDigitsMode mode = modes[j];
- df.setSignificantDigitsMode(mode);
- String expected = allExpected[(int)i][j];
- String actual = df.format(d);
- assertEquals("Significant digits mode getter is broken",
- mode, df.getSignificantDigitsMode());
- assertEquals("Significant digits output differs for "+i+", "+j,
- expected, actual);
- }
- }
- }
-
- @Test
- public void testParseNoExponent() throws ParseException {
- DecimalFormat df = new DecimalFormat();
- assertEquals("Parse no exponent has wrong default", false, df.getParseNoExponent());
- Number result1 = df.parse("123E4");
- df.setParseNoExponent(true);
- assertEquals("Parse no exponent getter is broken", true, df.getParseNoExponent());
- Number result2 = df.parse("123E4");
- assertEquals("Exponent did not parse before setParseNoExponent", result1, new Long(1230000));
- assertEquals("Exponent parsed after setParseNoExponent", result2, new Long(123));
- }
-
- @Test
- public void testMinimumGroupingDigits() {
- String[][] allExpected = {
- {"123", "123"},
- {"1,230", "1230"},
- {"12,300", "12,300"},
- {"1,23,000", "1,23,000"}
- };
-
- DecimalFormat df = new DecimalFormat("#,##,##0");
- assertEquals("Minimum grouping digits has wrong default", 1, df.getMinimumGroupingDigits());
-
- for (int l = 123, i=0; l <= 123000; l *= 10, i++) {
- df.setMinimumGroupingDigits(1);
- assertEquals("Minimum grouping digits getter is broken", 1, df.getMinimumGroupingDigits());
- String actual = df.format(l);
- assertEquals("Output is wrong for 1, "+i, allExpected[i][0], actual);
- df.setMinimumGroupingDigits(2);
- assertEquals("Minimum grouping digits getter is broken", 2, df.getMinimumGroupingDigits());
- actual = df.format(l);
- assertEquals("Output is wrong for 2, "+i, allExpected[i][1], actual);
- }
- }
-
- @Test
- public void testParseCaseSensitive() {
- String[] patterns = {"a#b", "A#B"};
- String[] inputs = {"a500b", "A500b", "a500B", "a500e10b", "a500E10b"};
- int[][] expectedParsePositions = {
- {5, 5, 5, 8, 8}, // case insensitive, pattern 0
- {5, 0, 4, 4, 8}, // case sensitive, pattern 0
- {5, 5, 5, 8, 8}, // case insensitive, pattern 1
- {0, 4, 0, 0, 0}, // case sensitive, pattern 1
- };
-
- for (int p = 0; p < patterns.length; p++) {
- String pat = patterns[p];
- DecimalFormat df = new DecimalFormat(pat);
- assertEquals("parseCaseSensitive default is wrong", false, df.getParseCaseSensitive());
- for (int i = 0; i < inputs.length; i++) {
- String inp = inputs[i];
- df.setParseCaseSensitive(false);
- assertEquals("parseCaseSensitive getter is broken", false, df.getParseCaseSensitive());
- ParsePosition actualInsensitive = new ParsePosition(0);
- df.parse(inp, actualInsensitive);
- assertEquals("Insensitive, pattern "+p+", input "+i,
- expectedParsePositions[p*2][i], actualInsensitive.getIndex());
- df.setParseCaseSensitive(true);
- assertEquals("parseCaseSensitive getter is broken", true, df.getParseCaseSensitive());
- ParsePosition actualSensitive = new ParsePosition(0);
- df.parse(inp, actualSensitive);
- assertEquals("Sensitive, pattern "+p+", input "+i,
- expectedParsePositions[p*2+1][i], actualSensitive.getIndex());
- }
- }
- }
}
# Basics
fp: "0.####" 0.10005 "0.1" 0.1
fp: - 0.10006 "0.1001" 0.1001
-pat: - "0.####"
+pat: - "#0.####"
fp: "#.####" 0.10005 "0.1" 0.1
-pat: - "0.####"
+pat: - "#0.####"
rt: "0" 1234 "1234"
-pat: - "0"
+pat: - "#0"
# Significant digits
fp: "@@@" 1.234567 "1.23" 1.23
pat: "##@@##" "@@##"
pat: "@@.@@" err # decimal sep. disallowed in sig. digits
-# The new pattern parser treats this the same as "@@#"
-#pat: "@#@" err # only one cluster of sig. digits
+pat: "@#@" err # only one cluster of sig. digits
pat: "@@0" err # either @ or 0, not both
# NumberRegression/Test4140009
* <p>
* In addition each attribute is listed in the fieldOrdering static array which specifies
* The order that attributes are printed whenever there is a test failure.
- * <p>
+ * <p>
* To add a new attribute, first create a public field for it.
* Next, add the attribute name to the fieldOrdering array.
* Finally, create a setter method for it.
- *
+ *
* @author rocketman
*/
-public class DataDrivenNumberFormatTestData {
-
+public class NumberFormatTestData {
+
/**
* The locale.
*/
public ULocale locale = null;
-
+
/**
* The currency.
*/
public Currency currency = null;
-
+
/**
* The pattern to initialize the formatter, for example 0.00"
*/
public String pattern = null;
-
+
/**
* The value to format as a string. For example 1234.5 would be "1234.5"
*/
public String format = null;
-
+
/**
* The formatted value.
*/
public String output = null;
-
+
/**
* Field for arbitrary comments.
*/
public String comment = null;
-
+
public Integer minIntegerDigits = null;
public Integer maxIntegerDigits = null;
public Integer minFractionDigits = null;
public String plural = null;
public Integer parseIntegerOnly = null;
public Integer decimalPatternMatchRequired = null;
- public Integer parseCaseSensitive = null;
public Integer parseNoExponent = null;
public String outputCurrency = null;
-
-
-
+
+
+
/**
* nothing or empty means that test ought to work for both C and JAVA;
* "C" means test is known to fail in C. "J" means test is known to fail in JAVA.
* "CJ" means test is known to fail for both languages.
*/
public String breaks = null;
-
+
private static Map<String, Integer> roundingModeMap =
new HashMap<String, Integer>();
-
+
static {
roundingModeMap.put("ceiling", BigDecimal.ROUND_CEILING);
roundingModeMap.put("floor", BigDecimal.ROUND_FLOOR);
roundingModeMap.put("halfUp", BigDecimal.ROUND_HALF_UP);
roundingModeMap.put("unnecessary", BigDecimal.ROUND_UNNECESSARY);
}
-
+
private static Map<String, Currency.CurrencyUsage> currencyUsageMap =
new HashMap<String, Currency.CurrencyUsage>();
-
+
static {
currencyUsageMap.put("standard", Currency.CurrencyUsage.STANDARD);
currencyUsageMap.put("cash", Currency.CurrencyUsage.CASH);
}
-
+
private static Map<String, Integer> padPositionMap =
new HashMap<String, Integer>();
-
+
static {
// TODO: Fix so that it doesn't depend on DecimalFormat.
padPositionMap.put("beforePrefix", DecimalFormat.PAD_BEFORE_PREFIX);
padPositionMap.put("beforeSuffix", DecimalFormat.PAD_BEFORE_SUFFIX);
padPositionMap.put("afterSuffix", DecimalFormat.PAD_AFTER_SUFFIX);
}
-
+
private static Map<String, Integer> formatStyleMap =
new HashMap<String, Integer>();
-
+
static {
formatStyleMap.put("decimal", NumberFormat.NUMBERSTYLE);
formatStyleMap.put("currency", NumberFormat.CURRENCYSTYLE);
formatStyleMap.put("currencyAccounting", NumberFormat.ACCOUNTINGCURRENCYSTYLE);
formatStyleMap.put("cashCurrency", NumberFormat.CASHCURRENCYSTYLE);
}
-
+
// Add any new fields here. On test failures, fields are printed in the same order they
// appear here.
private static String[] fieldOrdering = {
"parseNoExponent",
"outputCurrency"
};
-
+
static {
HashSet<String> set = new HashSet<String>();
for (String s : fieldOrdering) {
if (!set.add(s)) {
- throw new ExceptionInInitializerError(s + "is a duplicate field.");
+ throw new ExceptionInInitializerError(s + "is a duplicate field.");
}
}
}
-
+
private static <T> T fromString(Map<String, T> map, String key) {
T value = map.get(key);
if (value == null) {
}
return value;
}
-
+
// start field setters.
// add setter for each new field in this block.
-
+
public void setLocale(String value) {
locale = new ULocale(value);
}
-
+
public void setCurrency(String value) {
currency = Currency.getInstance(value);
}
-
+
public void setPattern(String value) {
pattern = value;
}
-
+
public void setFormat(String value) {
format = value;
}
-
+
public void setOutput(String value) {
output = value;
}
-
+
public void setComment(String value) {
comment = value;
}
-
+
public void setMinIntegerDigits(String value) {
minIntegerDigits = Integer.valueOf(value);
}
-
+
public void setMaxIntegerDigits(String value) {
maxIntegerDigits = Integer.valueOf(value);
}
-
+
public void setMinFractionDigits(String value) {
minFractionDigits = Integer.valueOf(value);
}
-
+
public void setMaxFractionDigits(String value) {
maxFractionDigits = Integer.valueOf(value);
}
-
+
public void setMinGroupingDigits(String value) {
minGroupingDigits = Integer.valueOf(value);
}
-
+
public void setBreaks(String value) {
breaks = value;
}
-
+
public void setUseSigDigits(String value) {
useSigDigits = Integer.valueOf(value);
}
-
+
public void setMinSigDigits(String value) {
minSigDigits = Integer.valueOf(value);
}
-
+
public void setMaxSigDigits(String value) {
maxSigDigits = Integer.valueOf(value);
}
-
+
public void setUseGrouping(String value) {
useGrouping = Integer.valueOf(value);
}
-
+
public void setMultiplier(String value) {
multiplier = Integer.valueOf(value);
}
-
+
public void setRoundingIncrement(String value) {
roundingIncrement = Double.valueOf(value);
}
-
+
public void setFormatWidth(String value) {
formatWidth = Integer.valueOf(value);
}
-
+
public void setPadCharacter(String value) {
padCharacter = value;
}
-
+
public void setUseScientific(String value) {
useScientific = Integer.valueOf(value);
}
-
+
public void setGrouping(String value) {
grouping = Integer.valueOf(value);
}
-
+
public void setGrouping2(String value) {
grouping2 = Integer.valueOf(value);
}
-
+
public void setRoundingMode(String value) {
roundingMode = fromString(roundingModeMap, value);
}
-
+
public void setCurrencyUsage(String value) {
currencyUsage = fromString(currencyUsageMap, value);
}
-
+
public void setMinimumExponentDigits(String value) {
minimumExponentDigits = Integer.valueOf(value);
}
-
+
public void setExponentSignAlwaysShown(String value) {
exponentSignAlwaysShown = Integer.valueOf(value);
}
-
+
public void setDecimalSeparatorAlwaysShown(String value) {
decimalSeparatorAlwaysShown = Integer.valueOf(value);
}
-
+
public void setPadPosition(String value) {
padPosition = fromString(padPositionMap, value);
}
-
+
public void setPositivePrefix(String value) {
positivePrefix = value;
}
-
+
public void setPositiveSuffix(String value) {
positiveSuffix = value;
}
-
+
public void setNegativePrefix(String value) {
negativePrefix = value;
}
-
+
public void setNegativeSuffix(String value) {
negativeSuffix = value;
}
-
+
public void setLocalizedPattern(String value) {
localizedPattern = value;
}
-
+
public void setToPattern(String value) {
toPattern = value;
}
-
+
public void setToLocalizedPattern(String value) {
toLocalizedPattern = value;
}
-
+
public void setStyle(String value) {
style = fromString(formatStyleMap, value);
}
-
+
public void setParse(String value) {
parse = value;
}
-
+
public void setLenient(String value) {
lenient = Integer.valueOf(value);
}
-
+
public void setPlural(String value) {
plural = value;
}
-
+
public void setParseIntegerOnly(String value) {
parseIntegerOnly = Integer.valueOf(value);
}
-
- public void setParseCaseSensitive(String value) {
- parseCaseSensitive = Integer.valueOf(value);
- }
-
+
public void setDecimalPatternMatchRequired(String value) {
decimalPatternMatchRequired = Integer.valueOf(value);
}
-
+
public void setParseNoExponent(String value) {
parseNoExponent = Integer.valueOf(value);
}
-
+
public void setOutputCurrency(String value) {
outputCurrency = value;
}
-
+
// end field setters.
-
+
// start of field clearers
// Add clear methods that can be set in one test and cleared
// in the next i.e the breaks field.
-
+
public void clearBreaks() {
breaks = null;
}
-
+
public void clearUseGrouping() {
useGrouping = null;
}
-
+
public void clearGrouping2() {
grouping2 = null;
}
-
+
public void clearGrouping() {
grouping = null;
}
-
+
public void clearMinGroupingDigits() {
minGroupingDigits = null;
}
-
+
public void clearUseScientific() {
useScientific = null;
}
-
+
public void clearDecimalSeparatorAlwaysShown() {
decimalSeparatorAlwaysShown = null;
}
-
+
// end field clearers
-
+
public void setField(String fieldName, String valueString)
throws NoSuchMethodException {
Method m = getClass().getMethod(
throw new RuntimeException(e);
}
}
-
+
public void clearField(String fieldName)
throws NoSuchMethodException {
Method m = getClass().getMethod(fieldToClearer(fieldName));
throw new RuntimeException(e);
}
}
-
- @Override
- public String toString() {
+
+ public String toString() {
StringBuilder result = new StringBuilder();
result.append("{");
boolean first = true;
+ Character.toUpperCase(fieldName.charAt(0))
+ fieldName.substring(1);
}
-
+
private static String fieldToClearer(String fieldName) {
return "clear"
+ Character.toUpperCase(fieldName.charAt(0))
* Corporation and others. All Rights Reserved.
**/
-/**
+/**
* Port From: JDK 1.4b1 : java.text.Format.NumberRegression
* Source File: java/text/format/NumberRegression.java
**/
-
+
/**
* @test 1.49 01/05/21
* @bug 4052223 4059870 4061302 4062486 4066646 4068693 4070798 4071005 4071014
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
try {
dfFoo.applyPattern("0000;-000");
- if (!dfFoo.toPattern().equals("0000"))
+ if (!dfFoo.toPattern().equals("#0000"))
errln("dfFoo.toPattern : " + dfFoo.toPattern());
logln(dfFoo.format(42));
logln(dfFoo.format(-42));
dfFoo.applyPattern("000;-000");
- if (!dfFoo.toPattern().equals("000"))
+ if (!dfFoo.toPattern().equals("#000"))
errln("dfFoo.toPattern : " + dfFoo.toPattern());
logln(dfFoo.format(42));
logln(dfFoo.format(-42));
dfFoo.applyPattern("000;-0000");
- if (!dfFoo.toPattern().equals("000"))
+ if (!dfFoo.toPattern().equals("#000"))
errln("dfFoo.toPattern : " + dfFoo.toPattern());
logln(dfFoo.format(42));
logln(dfFoo.format(-42));
dfFoo.applyPattern("0000;-000");
- if (!dfFoo.toPattern().equals("0000"))
+ if (!dfFoo.toPattern().equals("#0000"))
errln("dfFoo.toPattern : " + dfFoo.toPattern());
logln(dfFoo.format(42));
logln(dfFoo.format(-42));
errln("getMaximumIntegerDigits() returns " +
nf.getMaximumIntegerDigits());
}
-
+
/**
* Locale data should use generic currency symbol
*
public void Test4122840()
{
Locale[] locales = NumberFormat.getAvailableLocales();
-
+
for (int i = 0; i < locales.length; i++) {
ICUResourceBundle rb = (ICUResourceBundle)ICUResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME,locales[i]);
" does not contain generic currency symbol:" +
pattern );
}
-
+
// Create a DecimalFormat using the pattern we got and format a number
DecimalFormatSymbols symbols = new DecimalFormatSymbols(locales[i]);
DecimalFormat fmt1 = new DecimalFormat(pattern, symbols);
-
+
String result1 = fmt1.format(1.111);
-
+
//
// Now substitute in the locale's currency symbol and create another
// pattern. Replace the decimal separator with the monetary separator.
for (int j = 0; j < buf.length(); j++) {
if (buf.charAt(j) == '\u00a4') {
String cur = "'" + symbols.getCurrencySymbol() + "'";
- buf.replace(j, j+1, cur);
+ buf.replace(j, j+1, cur);
j += cur.length() - 1;
}
}
DecimalFormat fmt2 = new DecimalFormat(buf.toString(), symbols);
// Actual width of decimal fractions and rounding option are inherited
- // from the currency, not the pattern itself. So we need to force
+ // from the currency, not the pattern itself. So we need to force
// maximum/minimumFractionDigits and rounding option for the second
// DecimalForamt instance. The fix for ticket#7282 requires this test
// code change to make it work properly.
- if (symbols.getCurrency() != null) {
- fmt2.setMaximumFractionDigits(symbols.getCurrency().getDefaultFractionDigits());
- fmt2.setMinimumFractionDigits(symbols.getCurrency().getDefaultFractionDigits());
- fmt2.setRoundingIncrement(symbols.getCurrency().getRoundingIncrement());
- } else {
- fmt2.setMaximumFractionDigits(fmt1.getMinimumFractionDigits());
- fmt2.setMinimumFractionDigits(fmt1.getMaximumFractionDigits());
- fmt2.setRoundingIncrement(fmt1.getRoundingIncrement());
- }
+ fmt2.setMaximumFractionDigits(fmt1.getMaximumFractionDigits());
+ fmt2.setMinimumFractionDigits(fmt1.getMinimumFractionDigits());
+ fmt2.setRoundingIncrement(fmt1.getRoundingIncrement());
String result2 = fmt2.format(1.111);
-
+
// NOTE: en_IN is a special case (ChoiceFormat currency display name)
if (!result1.equals(result2) &&
!locales[i].toString().equals("en_IN")) {
}
}
}
-
+
/**
* DecimalFormat.format() delivers wrong string.
*/
@Test
public void Test4134034() {
DecimalFormat nf = new DecimalFormat("##,###,###.00");
-
+
String f = nf.format(9.02);
if (f.equals("9.02")) logln(f + " ok"); else errln("9.02 -> " + f + "; want 9.02");
*
* JDK 1.1.6 Bug, did NOT occur in 1.1.5
* Possibly related to bug 4125885.
- *
+ *
* This class demonstrates a regression in version 1.1.6
* of DecimalFormat class.
- *
+ *
* 1.1.6 Results
* Value 1.2 Format #.00 Result '01.20' !!!wrong
* Value 1.2 Format 0.00 Result '001.20' !!!wrong
* Value 1.2 Format 00.00 Result '0001.20' !!!wrong
* Value 1.2 Format #0.0# Result '1.2'
* Value 1.2 Format #0.00 Result '001.20' !!!wrong
- *
+ *
* 1.1.5 Results
* Value 1.2 Format #.00 Result '1.20'
* Value 1.2 Format 0.00 Result '1.20'
String out = nf.format(pi);
String pat = nf.toPattern();
double val = nf.parse(out).doubleValue();
-
+
nf.applyPattern(pat);
String out2 = nf.format(pi);
String pat2 = nf.toPattern();
double val2 = nf.parse(out2).doubleValue();
-
+
if (!pat.equals(pat2))
errln("Fail with \"" + PATS[i] + "\": Patterns should concur, \"" +
pat + "\" vs. \"" + pat2 + "\"");
logln("Applying pattern \"" + pattern + "\"");
sdf.applyPattern(pattern);
int minIntDig = sdf.getMinimumIntegerDigits();
- // In ICU 58 and older, this case returned 0.
- // Now it returns 1 instead, since the pattern parser enforces at least 1 min digit.
- if (minIntDig != 1) {
+ if (minIntDig != 0) {
errln("Test failed");
errln(" Minimum integer digits : " + minIntDig);
errln(" new pattern: " + sdf.toPattern());
e.printStackTrace();
}
logln("The string " + s + " parsed as " + n);
- assertEquals("Round trip failure", dbl, n.doubleValue());
+ if (n.doubleValue() != dbl) {
+ errln("Round trip failure");
+ }
}
/**
@Test
public void Test4167494() throws Exception {
NumberFormat fmt = NumberFormat.getInstance(Locale.US);
-
+
double a = Double.MAX_VALUE;
String s = fmt.format(a);
double b = fmt.parse(s).doubleValue();
@Test
public void Test4176114() {
String[] DATA = {
- "00", "00",
- "000", "000", // No grouping
- "#000", "000", // No grouping
+ "00", "#00",
+ "000", "#000", // No grouping
+ "#000", "#000", // No grouping
"#,##0", "#,##0",
"#,000", "#,000",
- "0,000", "0,000",
- "00,000", "00,000",
- "000,000", "000,000",
- "0,000,000,000,000.0000", "0,000,000,000,000.0000", // Reported
+ "0,000", "#0,000",
+ "00,000", "#00,000",
+ "000,000", "#,000,000",
+ "0,000,000,000,000.0000", "#0,000,000,000,000.0000", // Reported
};
for (int i=0; i<DATA.length; i+=2) {
DecimalFormat df = new DecimalFormat(DATA[i]);
}
}
+ @Test
+ public void Test4185761() throws IOException, ClassNotFoundException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(baos);
+
+ NumberFormat nf = NumberFormat.getInstance(Locale.US);
+
+ // Set special values we are going to search for in the output byte stream
+ // These are all legal values.
+ nf.setMinimumIntegerDigits(0x111); // Keep under 309
+ nf.setMaximumIntegerDigits(0x112); // Keep under 309
+ nf.setMinimumFractionDigits(0x113); // Keep under 340
+ nf.setMaximumFractionDigits(0x114); // Keep under 340
+
+ oos.writeObject(nf);
+ oos.flush();
+ baos.close();
+
+ byte[] bytes = baos.toByteArray();
+
+ // Scan for locations of min/max int/fract values in the byte array.
+ // At the moment (ICU4J 2.1), there is only one instance of each target pair
+ // in the byte stream, so assume first match is it. Note this is not entirely
+ // failsafe, and needs to be checked if we change the package or structure of
+ // this class.
+ // Current positions are 890, 880, 886, 876
+ int[] offsets = new int[4];
+ for (int i = 0; i < bytes.length - 1; ++i) {
+ if (bytes[i] == 0x01) { // high byte
+ for (int j = 0; j < offsets.length; ++j) {
+ if ((offsets[j] == 0) && (bytes[i+1] == (0x11 + j))) { // low byte
+ offsets[j] = i;
+ break;
+ }
+ }
+ }
+ }
+
+ {
+ ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
+ Object o = ois.readObject();
+ ois.close();
+
+ if (!nf.equals(o)) {
+ errln("Fail: NumberFormat serialization/equality bug");
+ } else {
+ logln("NumberFormat serialization/equality is OKAY.");
+ }
+ }
+
+ // Change the values in the byte stream so that min > max.
+ // Numberformat should catch this and throw an exception.
+ for (int i = 0; i < offsets.length; ++i) {
+ bytes[offsets[i]] = (byte)(4 - i);
+ }
+
+ {
+ ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
+ try {
+ NumberFormat format = (NumberFormat) ois.readObject();
+ logln("format: " + format.format(1234.56)); //fix "The variable is never used"
+ errln("FAIL: Deserialized bogus NumberFormat with minXDigits > maxXDigits");
+ } catch (InvalidObjectException e) {
+ logln("Ok: " + e.getMessage());
+ }
+ }
+
+ // Set values so they are too high, but min <= max
+ // Format should pass the min <= max test, and DecimalFormat should reset to current maximum
+ // (for compatibility with versions streamed out before the maximums were imposed).
+ for (int i = 0; i < offsets.length; ++i) {
+ bytes[offsets[i]] = 4;
+ }
+
+ {
+ ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
+ NumberFormat format = (NumberFormat) ois.readObject();
+ //For compatibility with previous version
+ if ((format.getMaximumIntegerDigits() != 309)
+ || format.getMaximumFractionDigits() != 340) {
+ errln("FAIL: Deserialized bogus NumberFormat with values out of range," +
+ " intMin: " + format.getMinimumIntegerDigits() +
+ " intMax: " + format.getMaximumIntegerDigits() +
+ " fracMin: " + format.getMinimumFractionDigits() +
+ " fracMax: " + format.getMaximumFractionDigits());
+ } else {
+ logln("Ok: Digit count out of range");
+ }
+ }
+ }
+
+
/**
* Some DecimalFormatSymbols changes are not picked up by DecimalFormat.
* This includes the minus sign, currency symbol, international currency
fmt.getPositiveSuffix() + ", exp ^");
}
sym.setPercent('%');
-
+
fmt.applyPattern("#\u2030");
sym.setPerMill('^');
fmt.setDecimalFormatSymbols(sym);
System.out.println("\n Test skipped for release 2.2");
return;
}
-
+
// Since the pattern logic has changed, make sure that patterns round
// trip properly. Test stream in/out integrity too.
Locale[] avail = NumberFormat.getAvailableLocales();
break;
}
DecimalFormat df = (DecimalFormat) nf;
-
+
// Test toPattern/applyPattern round trip
String pat = df.toPattern();
DecimalFormatSymbols symb = new DecimalFormatSymbols(avail[i]);
DecimalFormat f2 = new DecimalFormat(pat, symb);
- f2.setCurrency(df.getCurrency()); // Currency does not travel with the pattern string
if (!df.equals(f2)) {
errln("FAIL: " + avail[i] + " #" + j + " -> \"" + pat +
"\" -> \"" + f2.toPattern() + '"');
pat = df.toLocalizedPattern();
try{
f2.applyLocalizedPattern(pat);
-
+
String s1 = f2.format(123456);
String s2 = df.format(123456);
if(!s1.equals(s2)){
errln("FAIL: " + avail[i] + " #" + j + " -> localized \"" + s2 +
"\" -> \"" + s2 + '"'+ " in locale "+df.getLocale(ULocale.ACTUAL_LOCALE));
-
+
}
-
- // Equality of formatter objects is NOT guaranteed across toLocalizedPattern/applyLocalizedPattern.
- // However, equality of relevant properties is guaranteed.
- assertEquals("Localized FAIL on posPrefix", df.getPositivePrefix(), f2.getPositivePrefix());
- assertEquals("Localized FAIL on posSuffix", df.getPositiveSuffix(), f2.getPositiveSuffix());
- assertEquals("Localized FAIL on negPrefix", df.getNegativePrefix(), f2.getNegativePrefix());
- assertEquals("Localized FAIL on negSuffix", df.getNegativeSuffix(), f2.getNegativeSuffix());
- assertEquals("Localized FAIL on groupingSize", df.getGroupingSize(), f2.getGroupingSize());
- assertEquals("Localized FAIL on secondaryGroupingSize", df.getSecondaryGroupingSize(), f2.getSecondaryGroupingSize());
- assertEquals("Localized FAIL on minFrac", df.getMinimumFractionDigits(), f2.getMinimumFractionDigits());
- assertEquals("Localized FAIL on maxFrac", df.getMaximumFractionDigits(), f2.getMaximumFractionDigits());
- assertEquals("Localized FAIL on minInt", df.getMinimumIntegerDigits(), f2.getMinimumIntegerDigits());
- assertEquals("Localized FAIL on maxInt", df.getMaximumIntegerDigits(), f2.getMaximumIntegerDigits());
-
+ if (!df.equals(f2)) {
+ errln("FAIL: " + avail[i] + " #" + j + " -> localized \"" + pat +
+ "\" -> \"" + f2.toLocalizedPattern() + '"'+ " in locale "+df.getLocale(ULocale.ACTUAL_LOCALE));
+ errln("s1: "+s1+" s2: "+s2);
+ }
+
}catch(IllegalArgumentException ex){
- throw new AssertionError("For locale " + avail[i], ex);
+ errln(ex.getMessage()+" for locale "+ df.getLocale(ULocale.ACTUAL_LOCALE));
}
-
+
// Test writeObject/readObject round trip
ByteArrayOutputStream baos = new ByteArrayOutputStream();
sym = new DecimalFormatSymbols(Locale.US);
for (int j=0; j<SPECIALS.length; ++j) {
char special = SPECIALS[j];
- String pat = "'" + special + "'0'" + special + "'";
+ String pat = "'" + special + "'#0'" + special + "'";
try {
fmt = new DecimalFormat(pat, sym);
String pat2 = fmt.toPattern();
String str = Long.toString(DATA[i]);
for (int m = 1; m <= 100; m++) {
fmt.setMultiplier(m);
- long n = fmt.parse(str).longValue();
+ long n = ((Number) fmt.parse(str)).longValue();
if (n > 0 != DATA[i] > 0) {
errln("\"" + str + "\" parse(x " + fmt.getMultiplier() +
") => " + n);
new Double(1.006), "1.01",
};
NumberFormat fmt = NumberFormat.getInstance(Locale.US);
- fmt.setMaximumFractionDigits(2);
+ fmt.setMaximumFractionDigits(2);
for (int i=0; i<DATA.length; i+=2) {
String s = fmt.format(((Double) DATA[i]).doubleValue());
if (!s.equals(DATA[i+1])) {
- errln("FAIL: Got " + s + ", exp " + DATA[i+1]);
+ errln("FAIL: Got " + s + ", exp " + DATA[i+1]);
}
}
}
-
+
/**
* 4243011: Formatting .5 rounds to "1" instead of "0"
*/
public void Test4243011() {
double DATA[] = {0.5, 1.5, 2.5, 3.5, 4.5};
String EXPECTED[] = {"0.", "2.", "2.", "4.", "4."};
-
+
DecimalFormat format = new DecimalFormat("0.");
for (int i = 0; i < DATA.length; i++) {
String result = format.format(DATA[i]);
}
}
}
-
+
/**
* 4243108: format(0.0) gives "0.1" if preceded by parse("99.99")
*/
} catch (ParseException e) {
errln("Caught a ParseException:");
e.printStackTrace();
- }
+ }
result = f.format(0.0);
if (result.equals("0")) {
logln("OK: got " + result);
errln("FAIL: got " + result);
}
}
-
+
/**
* 4330377: DecimalFormat engineering notation gives incorrect results
*/
}
*/
}
-
+
/**
* 4233840: NumberFormat does not round correctly
*/
NumberFormat nf = new DecimalFormat("0.##", new DecimalFormatSymbols(Locale.US));
nf.setMinimumFractionDigits(2);
-
+
String result = nf.format(f);
-
+
if (!result.equals("0.01")) {
errln("FAIL: input: " + f + ", expected: 0.01, got: " + result);
}
}
-
+
/**
* 4241880: Decimal format doesnt round a double properly when the number is less than 1
*/
* For serialization
*/
private static final long serialVersionUID = 1251303884737169952L;
- @Override
- public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) {
+ public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) {
return new StringBuffer("");
}
- @Override
- public StringBuffer format(long number,StringBuffer toAppendTo, FieldPosition pos) {
+ public StringBuffer format(long number,StringBuffer toAppendTo, FieldPosition pos) {
return new StringBuffer("");
}
- @Override
- public Number parse(String text, ParsePosition parsePosition) {
+ public Number parse(String text, ParsePosition parsePosition) {
return new Integer(0);
}
- @Override
- public StringBuffer format(java.math.BigDecimal number, StringBuffer toAppendTo, FieldPosition pos) {
+ public StringBuffer format(java.math.BigDecimal number, StringBuffer toAppendTo, FieldPosition pos) {
return new StringBuffer("");
}
- @Override
- public StringBuffer format(BigInteger number, StringBuffer toAppendTo, FieldPosition pos) {
+ public StringBuffer format(BigInteger number, StringBuffer toAppendTo, FieldPosition pos) {
return new StringBuffer("");
}
- @Override
- public StringBuffer format(com.ibm.icu.math.BigDecimal number, StringBuffer toAppendTo, FieldPosition pos) {
+ public StringBuffer format(com.ibm.icu.math.BigDecimal number, StringBuffer toAppendTo, FieldPosition pos) {
return new StringBuffer("");
}
}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.dev.test.format;
-
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.text.ParseException;
-import java.text.ParsePosition;
-
-import com.ibm.icu.dev.test.format.DataDrivenNumberFormatTestUtility.CodeUnderTest;
-import com.ibm.icu.impl.number.Endpoint;
-import com.ibm.icu.impl.number.Format;
-import com.ibm.icu.impl.number.FormatQuantity;
-import com.ibm.icu.impl.number.FormatQuantity1;
-import com.ibm.icu.impl.number.FormatQuantity2;
-import com.ibm.icu.impl.number.FormatQuantity3;
-import com.ibm.icu.impl.number.FormatQuantity4;
-import com.ibm.icu.impl.number.Parse;
-import com.ibm.icu.impl.number.Parse.ParseMode;
-import com.ibm.icu.impl.number.PatternString;
-import com.ibm.icu.impl.number.Properties;
-import com.ibm.icu.impl.number.formatters.PaddingFormat.PadPosition;
-import com.ibm.icu.text.DecimalFormat;
-import com.ibm.icu.text.DecimalFormat.PropertySetter;
-import com.ibm.icu.text.DecimalFormatSymbols;
-import com.ibm.icu.util.CurrencyAmount;
-import com.ibm.icu.util.ULocale;
-
-public class ShanesDataDrivenTester extends CodeUnderTest {
- static final String dataPath =
- "../../../icu4j-core-tests/src/com/ibm/icu/dev/data/numberformattestspecification.txt";
-
- public static void run() {
- CodeUnderTest tester = new ShanesDataDrivenTester();
- DataDrivenNumberFormatTestUtility.runFormatSuiteIncludingKnownFailures(dataPath, tester);
- }
-
- @Override
- public Character Id() {
- return 'S';
- }
-
- /**
- * Runs a single formatting test. On success, returns null. On failure, returns the error. This
- * implementation just returns null. Subclasses should override.
- *
- * @param tuple contains the parameters of the format test.
- */
- @Override
- public String format(DataDrivenNumberFormatTestData tuple) {
- String pattern = (tuple.pattern == null) ? "0" : tuple.pattern;
- ULocale locale = (tuple.locale == null) ? ULocale.ENGLISH : tuple.locale;
- Properties properties = PatternString.parseToProperties(pattern, tuple.currency != null);
- propertiesFromTuple(tuple, properties);
- Format fmt = Endpoint.fromBTA(properties, locale);
- FormatQuantity q1, q2, q3;
- if (tuple.format.equals("NaN")) {
- q1 = q2 = new FormatQuantity1(Double.NaN);
- q3 = new FormatQuantity2(Double.NaN);
- } else if (tuple.format.equals("-Inf")) {
- q1 = q2 = new FormatQuantity1(Double.NEGATIVE_INFINITY);
- q3 = new FormatQuantity1(Double.NEGATIVE_INFINITY);
- } else if (tuple.format.equals("Inf")) {
- q1 = q2 = new FormatQuantity1(Double.POSITIVE_INFINITY);
- q3 = new FormatQuantity1(Double.POSITIVE_INFINITY);
- } else {
- BigDecimal d = new BigDecimal(tuple.format);
- if (d.precision() <= 16) {
- q1 = new FormatQuantity1(d);
- q2 = new FormatQuantity1(Double.parseDouble(tuple.format));
- q3 = new FormatQuantity4(d);
- } else {
- q1 = new FormatQuantity1(d);
- q2 = new FormatQuantity3(d);
- q3 = new FormatQuantity4(d); // duplicate values so no null
- }
- }
- String expected = tuple.output;
- String actual1 = fmt.format(q1);
- if (!expected.equals(actual1)) {
- return "Expected \"" + expected + "\", got \"" + actual1 + "\" on FormatQuantity1 BigDecimal";
- }
- String actual2 = fmt.format(q2);
- if (!expected.equals(actual2)) {
- return "Expected \"" + expected + "\", got \"" + actual2 + "\" on FormatQuantity1 double";
- }
- String actual3 = fmt.format(q3);
- if (!expected.equals(actual3)) {
- return "Expected \"" + expected + "\", got \"" + actual3 + "\" on FormatQuantity4 BigDecimal";
- }
- return null;
- }
-
- /**
- * Runs a single toPattern test. On success, returns null. On failure, returns the error. This
- * implementation just returns null. Subclasses should override.
- *
- * @param tuple contains the parameters of the format test.
- */
- @Override
- public String toPattern(DataDrivenNumberFormatTestData tuple) {
- String pattern = (tuple.pattern == null) ? "0" : tuple.pattern;
- final Properties properties;
- DecimalFormat df;
- try {
- properties = PatternString.parseToProperties(pattern, tuple.currency != null);
- propertiesFromTuple(tuple, properties);
- // TODO: Use PatternString.propertiesToString() directly. (How to deal with CurrencyUsage?)
- df = new DecimalFormat();
- df.setProperties(
- new PropertySetter() {
- @Override
- public void set(Properties props) {
- props.copyFrom(properties);
- }
- });
- } catch (IllegalArgumentException e) {
- e.printStackTrace();
- return e.getLocalizedMessage();
- }
-
- if (tuple.toPattern != null) {
- String expected = tuple.toPattern;
- String actual = df.toPattern();
- if (!expected.equals(actual)) {
- return "Expected toPattern='" + expected + "'; got '" + actual + "'";
- }
- }
- if (tuple.toLocalizedPattern != null) {
- String expected = tuple.toLocalizedPattern;
- String actual = PatternString.propertiesToString(properties);
- if (!expected.equals(actual)) {
- return "Expected toLocalizedPattern='" + expected + "'; got '" + actual + "'";
- }
- }
- return null;
- }
-
- /**
- * Runs a single parse test. On success, returns null. On failure, returns the error. This
- * implementation just returns null. Subclasses should override.
- *
- * @param tuple contains the parameters of the format test.
- */
- @Override
- public String parse(DataDrivenNumberFormatTestData tuple) {
- String pattern = (tuple.pattern == null) ? "0" : tuple.pattern;
- Properties properties;
- ParsePosition ppos = new ParsePosition(0);
- Number actual;
- try {
- properties = PatternString.parseToProperties(pattern, tuple.currency != null);
- propertiesFromTuple(tuple, properties);
- actual =
- Parse.parse(
- tuple.parse, ppos, properties, DecimalFormatSymbols.getInstance(tuple.locale));
- } catch (IllegalArgumentException e) {
- return "parse exception: " + e.getMessage();
- }
- if (actual == null && ppos.getIndex() != 0) {
- throw new AssertionError("Error: value is null but parse position is not zero");
- }
- if (ppos.getIndex() == 0) {
- return "Parse failed; got " + actual + ", but expected " + tuple.output;
- }
- if (tuple.output.equals("NaN")) {
- if (!Double.isNaN(actual.doubleValue())) {
- return "Expected NaN, but got: " + actual;
- }
- return null;
- } else if (tuple.output.equals("Inf")) {
- if (!Double.isInfinite(actual.doubleValue())
- || Double.compare(actual.doubleValue(), 0.0) < 0) {
- return "Expected Inf, but got: " + actual;
- }
- return null;
- } else if (tuple.output.equals("-Inf")) {
- if (!Double.isInfinite(actual.doubleValue())
- || Double.compare(actual.doubleValue(), 0.0) > 0) {
- return "Expected -Inf, but got: " + actual;
- }
- return null;
- } else if (tuple.output.equals("fail")) {
- return null;
- } else if (new BigDecimal(tuple.output).compareTo(new BigDecimal(actual.toString())) != 0) {
- return "Expected: " + tuple.output + ", got: " + actual;
- } else {
- return null;
- }
- }
-
- /**
- * Runs a single parse currency test. On success, returns null. On failure, returns the error.
- * This implementation just returns null. Subclasses should override.
- *
- * @param tuple contains the parameters of the format test.
- */
- @Override
- public String parseCurrency(DataDrivenNumberFormatTestData tuple) {
- String pattern = (tuple.pattern == null) ? "0" : tuple.pattern;
- Properties properties;
- ParsePosition ppos = new ParsePosition(0);
- CurrencyAmount actual;
- try {
- properties = PatternString.parseToProperties(pattern, tuple.currency != null);
- propertiesFromTuple(tuple, properties);
- actual =
- Parse.parseCurrency(
- tuple.parse, ppos, properties, DecimalFormatSymbols.getInstance(tuple.locale));
- } catch (ParseException e) {
- e.printStackTrace();
- return "parse exception: " + e.getMessage();
- }
- if (ppos.getIndex() == 0 || actual.getCurrency().getCurrencyCode().equals("XXX")) {
- return "Parse failed; got " + actual + ", but expected " + tuple.output;
- }
- BigDecimal expectedNumber = new BigDecimal(tuple.output);
- if (expectedNumber.compareTo(new BigDecimal(actual.getNumber().toString())) != 0) {
- return "Wrong number: Expected: " + expectedNumber + ", got: " + actual;
- }
- String expectedCurrency = tuple.outputCurrency;
- if (!expectedCurrency.equals(actual.getCurrency().toString())) {
- return "Wrong currency: Expected: " + expectedCurrency + ", got: " + actual;
- }
- return null;
- }
-
- /**
- * Runs a single select test. On success, returns null. On failure, returns the error. This
- * implementation just returns null. Subclasses should override.
- *
- * @param tuple contains the parameters of the format test.
- */
- @Override
- public String select(DataDrivenNumberFormatTestData tuple) {
- return null;
- }
-
- private static void propertiesFromTuple(
- DataDrivenNumberFormatTestData tuple, Properties properties) {
- if (tuple.minIntegerDigits != null) {
- properties.setMinimumIntegerDigits(tuple.minIntegerDigits);
- }
- if (tuple.maxIntegerDigits != null) {
- properties.setMaximumIntegerDigits(tuple.maxIntegerDigits);
- }
- if (tuple.minFractionDigits != null) {
- properties.setMinimumFractionDigits(tuple.minFractionDigits);
- }
- if (tuple.maxFractionDigits != null) {
- properties.setMaximumFractionDigits(tuple.maxFractionDigits);
- }
- if (tuple.currency != null) {
- properties.setCurrency(tuple.currency);
- }
- if (tuple.minGroupingDigits != null) {
- properties.setMinimumGroupingDigits(tuple.minGroupingDigits);
- }
- if (tuple.useSigDigits != null) {
- // TODO
- }
- if (tuple.minSigDigits != null) {
- properties.setMinimumSignificantDigits(tuple.minSigDigits);
- }
- if (tuple.maxSigDigits != null) {
- properties.setMaximumSignificantDigits(tuple.maxSigDigits);
- }
- if (tuple.useGrouping != null && tuple.useGrouping == 0) {
- properties.setGroupingSize(Integer.MAX_VALUE);
- properties.setSecondaryGroupingSize(Integer.MAX_VALUE);
- }
- if (tuple.multiplier != null) {
- properties.setMultiplier(new BigDecimal(tuple.multiplier));
- }
- if (tuple.roundingIncrement != null) {
- properties.setRoundingIncrement(new BigDecimal(tuple.roundingIncrement.toString()));
- }
- if (tuple.formatWidth != null) {
- properties.setFormatWidth(tuple.formatWidth);
- }
- if (tuple.padCharacter != null && tuple.padCharacter.length() > 0) {
- properties.setPadString(tuple.padCharacter.toString());
- }
- if (tuple.useScientific != null) {
- properties.setMinimumExponentDigits(
- tuple.useScientific != 0 ? 1 : Properties.DEFAULT_MINIMUM_EXPONENT_DIGITS);
- }
- if (tuple.grouping != null) {
- properties.setGroupingSize(tuple.grouping);
- }
- if (tuple.grouping2 != null) {
- properties.setSecondaryGroupingSize(tuple.grouping2);
- }
- if (tuple.roundingMode != null) {
- properties.setRoundingMode(RoundingMode.valueOf(tuple.roundingMode));
- }
- if (tuple.currencyUsage != null) {
- properties.setCurrencyUsage(tuple.currencyUsage);
- }
- if (tuple.minimumExponentDigits != null) {
- properties.setMinimumExponentDigits(tuple.minimumExponentDigits.byteValue());
- }
- if (tuple.exponentSignAlwaysShown != null) {
- properties.setExponentSignAlwaysShown(tuple.exponentSignAlwaysShown != 0);
- }
- if (tuple.decimalSeparatorAlwaysShown != null) {
- properties.setDecimalSeparatorAlwaysShown(tuple.decimalSeparatorAlwaysShown != 0);
- }
- if (tuple.padPosition != null) {
- properties.setPadPosition(PadPosition.fromOld(tuple.padPosition));
- }
- if (tuple.positivePrefix != null) {
- properties.setPositivePrefix(tuple.positivePrefix);
- }
- if (tuple.positiveSuffix != null) {
- properties.setPositiveSuffix(tuple.positiveSuffix);
- }
- if (tuple.negativePrefix != null) {
- properties.setNegativePrefix(tuple.negativePrefix);
- }
- if (tuple.negativeSuffix != null) {
- properties.setNegativeSuffix(tuple.negativeSuffix);
- }
- if (tuple.localizedPattern != null) {
- // TODO
- }
- if (tuple.lenient != null) {
- properties.setParseMode(tuple.lenient == 0 ? ParseMode.STRICT : ParseMode.LENIENT);
- }
- if (tuple.parseIntegerOnly != null) {
- properties.setParseIntegerOnly(tuple.parseIntegerOnly != 0);
- }
- if (tuple.parseCaseSensitive != null) {
- properties.setParseCaseSensitive(tuple.parseCaseSensitive != 0);
- }
- if (tuple.decimalPatternMatchRequired != null) {
- properties.setDecimalPatternMatchRequired(tuple.decimalPatternMatchRequired != 0);
- }
- if (tuple.parseNoExponent != null) {
- properties.setParseNoExponent(tuple.parseNoExponent != 0);
- }
- }
-}
errln("parsed argument " + parsedArgs[0] + " != " + num);
}
}
- catch (ParseException e) {
- errln("parse of '" + result + "' returned exception: "
- + e.getMessage() + " " + e.getErrorOffset());
+ catch (Exception e) {
+ errln("parse of '" + result + " returned exception: " + e.getMessage());
}
}
}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.dev.test.numbers;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import org.junit.Test;
-
-import com.ibm.icu.impl.number.AffixPatternUtils;
-import com.ibm.icu.impl.number.NumberStringBuilder;
-import com.ibm.icu.text.DecimalFormatSymbols;
-import com.ibm.icu.util.ULocale;
-
-public class AffixPatternUtilsTest {
-
- @Test
- public void testEscape() {
- Object[][] cases = {
- {"", ""},
- {"abc", "abc"},
- {"-", "'-'"},
- {"-!", "'-'!"},
- {"−", "−"},
- {"---", "'---'"},
- {"-%-", "'-%-'"},
- {"'", "''"},
- {"-'", "'-'''"},
- {"-'-", "'-''-'"},
- {"a-'-", "a'-''-'"}
- };
-
- StringBuilder sb = new StringBuilder();
- for (Object[] cas : cases) {
- String input = (String) cas[0];
- String expected = (String) cas[1];
- sb.setLength(0);
- AffixPatternUtils.escape(input, sb);
- assertEquals(expected, sb.toString());
- }
- }
-
- @Test
- public void testUnescape() {
- Object[][] cases = {
- {"", false, 0, ""},
- {"abc", false, 3, "abc"},
- {"-", false, 1, "−"},
- {"-!", false, 2, "−!"},
- {"+", false, 1, "\u061C+"},
- {"+!", false, 2, "\u061C+!"},
- {"‰", false, 1, "؉"},
- {"‰!", false, 2, "؉!"},
- {"-x", false, 2, "−x"},
- {"'-'x", false, 2, "-x"},
- {"'--''-'-x", false, 6, "--'-−x"},
- {"''", false, 1, "'"},
- {"''''", false, 2, "''"},
- {"''''''", false, 3, "'''"},
- {"''x''", false, 3, "'x'"},
- {"¤", true, 1, "$"},
- {"¤¤", true, 2, "XXX"},
- {"¤¤¤", true, 3, "long name"},
- {"¤¤¤¤", true, 4, "\uFFFD"},
- {"¤¤¤¤¤", true, 5, "\uFFFD"},
- {"¤¤¤a¤¤¤¤", true, 8, "long namea\uFFFD"},
- {"a¤¤¤¤b¤¤¤¤¤c", true, 12, "a\uFFFDb\uFFFDc"},
- {"¤!", true, 2, "$!"},
- {"¤¤!", true, 3, "XXX!"},
- {"¤¤¤!", true, 4, "long name!"},
- {"-¤¤", true, 3, "−XXX"},
- {"¤¤-", true, 3, "XXX−"},
- {"'¤'", false, 1, "¤"},
- {"%", false, 1, "٪\u061C"},
- {"'%'", false, 1, "%"},
- {"¤'-'%", true, 3, "$-٪\u061C"}
- };
-
- // ar_SA has an interesting percent sign and various Arabic letter marks
- DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(new ULocale("ar_SA"));
- NumberStringBuilder sb = new NumberStringBuilder();
-
- for (Object[] cas : cases) {
- String input = (String) cas[0];
- boolean curr = (Boolean) cas[1];
- int length = (Integer) cas[2];
- String output = (String) cas[3];
-
- assertEquals(
- "Currency on <" + input + ">", curr, AffixPatternUtils.hasCurrencySymbols(input));
- assertEquals("Length on <" + input + ">", length, AffixPatternUtils.unescapedLength(input));
-
- sb.clear();
- AffixPatternUtils.unescape(input, symbols, "$", "XXX", "long name", "−", sb);
- assertEquals("Output on <" + input + ">", output, sb.toString());
- }
- }
-
- @Test
- public void testInvalid() {
- String[] invalidExamples = {"'", "x'", "'x", "'x''", "''x'"};
- DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(new ULocale("en_US"));
- NumberStringBuilder sb = new NumberStringBuilder();
-
- for (String str : invalidExamples) {
- try {
- AffixPatternUtils.hasCurrencySymbols(str);
- fail("No exception was thrown on an invalid string");
- } catch (IllegalArgumentException e) {
- // OK
- }
- try {
- AffixPatternUtils.unescapedLength(str);
- fail("No exception was thrown on an invalid string");
- } catch (IllegalArgumentException e) {
- // OK
- }
- try {
- AffixPatternUtils.unescape(str, symbols, "$", "XXX", "long name", "−", sb);
- fail("No exception was thrown on an invalid string");
- } catch (IllegalArgumentException e) {
- // OK
- }
- }
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.dev.test.numbers;
-
-import java.math.BigDecimal;
-import java.math.MathContext;
-import java.math.RoundingMode;
-import java.text.ParseException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.ThreadLocalRandom;
-
-import org.junit.Test;
-
-import com.ibm.icu.dev.test.TestFmwk;
-import com.ibm.icu.impl.number.Endpoint;
-import com.ibm.icu.impl.number.Format;
-import com.ibm.icu.impl.number.FormatQuantity;
-import com.ibm.icu.impl.number.FormatQuantity1;
-import com.ibm.icu.impl.number.FormatQuantity2;
-import com.ibm.icu.impl.number.FormatQuantity3;
-import com.ibm.icu.impl.number.FormatQuantity4;
-import com.ibm.icu.impl.number.Properties;
-import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
-
-/** TODO: This is a temporary name for this class. Suggestions for a better name? */
-public class FormatQuantityTest extends TestFmwk {
-
- @Test
- public void testBehavior() throws ParseException {
-
- // Make a list of several formatters to test the behavior of FormatQuantity.
- List<Format> formats = new ArrayList<Format>();
-
- Properties properties = new Properties();
- Format ndf = Endpoint.fromBTA(properties);
- formats.add(ndf);
-
- properties =
- new Properties()
- .setMinimumSignificantDigits(3)
- .setMaximumSignificantDigits(3)
- .setCompactStyle(CompactStyle.LONG);
- Format cdf = Endpoint.fromBTA(properties);
- formats.add(cdf);
-
- properties =
- new Properties()
- .setMinimumExponentDigits(1)
- .setMaximumIntegerDigits(3)
- .setMaximumFractionDigits(1);
- Format exf = Endpoint.fromBTA(properties);
- formats.add(exf);
-
- properties = new Properties().setRoundingIncrement(new BigDecimal("0.5"));
- Format rif = Endpoint.fromBTA(properties);
- formats.add(rif);
-
- String[] cases = {
- "1.0",
- "2.01",
- "1234.56",
- "3000.0",
- "0.00026418",
- "0.01789261",
- "468160.0",
- "999000.0",
- "999900.0",
- "999990.0",
- "0.0",
- "12345678901.0",
- "-5193.48",
- };
-
- String[] hardCases = {
- "9999999999999900.0",
- "789000000000000000000000.0",
- "789123123567853156372158.0",
- "987654321987654321987654321987654321987654311987654321.0",
- };
-
- String[] doubleCases = {
- "512.0000000000017",
- "4095.9999999999977",
- "4095.999999999998",
- "4095.9999999999986",
- "4095.999999999999",
- "4095.9999999999995",
- "4096.000000000001",
- "4096.000000000002",
- "4096.000000000003",
- "4096.000000000004",
- "4096.000000000005",
- "4096.0000000000055",
- "4096.000000000006",
- "4096.000000000007",
- };
-
- int i = 0;
- for (String str : cases) {
- testFormatQuantity(i++, str, formats, 0);
- }
-
- i = 0;
- for (String str : hardCases) {
- testFormatQuantity(i++, str, formats, 1);
- }
-
- i = 0;
- for (String str : doubleCases) {
- testFormatQuantity(i++, str, formats, 2);
- }
- }
-
- static void testFormatQuantity(int t, String str, List<Format> formats, int mode) {
- if (mode == 2) {
- assertEquals("Double is not valid", Double.toString(Double.parseDouble(str)), str);
- }
-
- List<FormatQuantity> qs = new ArrayList<FormatQuantity>();
- BigDecimal d = new BigDecimal(str);
- qs.add(new FormatQuantity1(d));
- if (mode == 0) qs.add(new FormatQuantity2(d));
- qs.add(new FormatQuantity3(d));
- qs.add(new FormatQuantity4(d));
-
- if (new BigDecimal(Double.toString(d.doubleValue())).compareTo(d) == 0) {
- double dv = d.doubleValue();
- qs.add(new FormatQuantity1(dv));
- if (mode == 0) qs.add(new FormatQuantity2(dv));
- qs.add(new FormatQuantity3(dv));
- qs.add(new FormatQuantity4(dv));
- }
-
- if (new BigDecimal(Long.toString(d.longValue())).compareTo(d) == 0) {
- double lv = d.longValue();
- qs.add(new FormatQuantity1(lv));
- if (mode == 0) qs.add(new FormatQuantity2(lv));
- qs.add(new FormatQuantity3(lv));
- qs.add(new FormatQuantity4(lv));
- }
-
- testFormatQuantityExpectedOutput(qs.get(0), str);
-
- if (qs.size() == 1) {
- return;
- }
-
- for (int i = 1; i < qs.size(); i++) {
- FormatQuantity q0 = qs.get(0);
- FormatQuantity q1 = qs.get(i);
- testFormatQuantityExpectedOutput(q1, str);
- testFormatQuantityRounding(q0, q1);
- testFormatQuantityRoundingInterval(q0, q1);
- testFormatQuantityMath(q0, q1);
- testFormatQuantityWithFormats(q0, q1, formats);
- }
- }
-
- private static void testFormatQuantityExpectedOutput(FormatQuantity rq, String expected) {
- StringBuilder sb = new StringBuilder();
- FormatQuantity q0 = rq.clone();
- // Force an accurate double
- q0.roundToInfinity();
- q0.setIntegerFractionLength(1, Integer.MAX_VALUE, 1, Integer.MAX_VALUE);
- for (int m = q0.getUpperDisplayMagnitude(); m >= q0.getLowerDisplayMagnitude(); m--) {
- sb.append(q0.getDigit(m));
- if (m == 0) sb.append('.');
- }
- if (q0.isNegative()) {
- sb.insert(0, '-');
- }
- String actual = sb.toString();
- assertEquals("Unexpected output from simple string conversion (" + q0 + ")", expected, actual);
- }
-
- private static final MathContext MATH_CONTEXT_HALF_EVEN =
- new MathContext(0, RoundingMode.HALF_EVEN);
- private static final MathContext MATH_CONTEXT_CEILING = new MathContext(0, RoundingMode.CEILING);
- private static final MathContext MATH_CONTEXT_PRECISION =
- new MathContext(3, RoundingMode.HALF_UP);
-
- private static void testFormatQuantityRounding(FormatQuantity rq0, FormatQuantity rq1) {
- FormatQuantity q0 = rq0.clone();
- FormatQuantity q1 = rq1.clone();
- q0.roundToMagnitude(-1, MATH_CONTEXT_HALF_EVEN);
- q1.roundToMagnitude(-1, MATH_CONTEXT_HALF_EVEN);
- testFormatQuantityBehavior(q0, q1);
-
- q0 = rq0.clone();
- q1 = rq1.clone();
- q0.roundToMagnitude(-1, MATH_CONTEXT_CEILING);
- q1.roundToMagnitude(-1, MATH_CONTEXT_CEILING);
- testFormatQuantityBehavior(q0, q1);
-
- q0 = rq0.clone();
- q1 = rq1.clone();
- q0.roundToMagnitude(-1, MATH_CONTEXT_PRECISION);
- q1.roundToMagnitude(-1, MATH_CONTEXT_PRECISION);
- testFormatQuantityBehavior(q0, q1);
- }
-
- private static void testFormatQuantityRoundingInterval(FormatQuantity rq0, FormatQuantity rq1) {
- FormatQuantity q0 = rq0.clone();
- FormatQuantity q1 = rq1.clone();
- q0.roundToIncrement(new BigDecimal("0.05"), MATH_CONTEXT_HALF_EVEN);
- q1.roundToIncrement(new BigDecimal("0.05"), MATH_CONTEXT_HALF_EVEN);
- testFormatQuantityBehavior(q0, q1);
-
- q0 = rq0.clone();
- q1 = rq1.clone();
- q0.roundToIncrement(new BigDecimal("0.05"), MATH_CONTEXT_CEILING);
- q1.roundToIncrement(new BigDecimal("0.05"), MATH_CONTEXT_CEILING);
- testFormatQuantityBehavior(q0, q1);
- }
-
- private static void testFormatQuantityMath(FormatQuantity rq0, FormatQuantity rq1) {
- FormatQuantity q0 = rq0.clone();
- FormatQuantity q1 = rq1.clone();
- q0.adjustMagnitude(-3);
- q1.adjustMagnitude(-3);
- testFormatQuantityBehavior(q0, q1);
-
- q0 = rq0.clone();
- q1 = rq1.clone();
- q0.multiplyBy(new BigDecimal("3.14159"));
- q1.multiplyBy(new BigDecimal("3.14159"));
- testFormatQuantityBehavior(q0, q1);
- }
-
- private static void testFormatQuantityWithFormats(
- FormatQuantity rq0, FormatQuantity rq1, List<Format> formats) {
- for (Format format : formats) {
- FormatQuantity q0 = rq0.clone();
- FormatQuantity q1 = rq1.clone();
- String s1 = format.format(q0);
- String s2 = format.format(q1);
- assertEquals("Different output from formatter (" + q0 + ", " + q1 + ")", s1, s2);
- }
- }
-
- private static void testFormatQuantityBehavior(FormatQuantity rq0, FormatQuantity rq1) {
- FormatQuantity q0 = rq0.clone();
- FormatQuantity q1 = rq1.clone();
-
- assertEquals("Different sign (" + q0 + ", " + q1 + ")", q0.isNegative(), q1.isNegative());
-
- assertEquals(
- "Different fingerprint (" + q0 + ", " + q1 + ")",
- q0.getPositionFingerprint(),
- q1.getPositionFingerprint());
-
- assertEquals(
- "Different upper range of digits (" + q0 + ", " + q1 + ")",
- q0.getUpperDisplayMagnitude(),
- q1.getUpperDisplayMagnitude());
-
- assertDoubleEquals(
- "Different double values (" + q0 + ", " + q1 + ")", q0.toDouble(), q1.toDouble());
-
- assertBigDecimalEquals(
- "Different BigDecimal values (" + q0 + ", " + q1 + ")",
- q0.toBigDecimal(),
- q1.toBigDecimal());
-
- int equalityDigits = Math.min(q0.maxRepresentableDigits(), q1.maxRepresentableDigits());
- for (int m = q0.getUpperDisplayMagnitude(), i = 0;
- m >= Math.min(q0.getLowerDisplayMagnitude(), q1.getLowerDisplayMagnitude())
- && i < equalityDigits;
- m--, i++) {
- assertEquals(
- "Different digit at magnitude " + m + " (" + q0 + ", " + q1 + ")",
- q0.getDigit(m),
- q1.getDigit(m));
- }
-
- if (rq0 instanceof FormatQuantity4) {
- String message = ((FormatQuantity4) rq0).checkHealth();
- if (message != null) errln(message);
- }
- if (rq1 instanceof FormatQuantity4) {
- String message = ((FormatQuantity4) rq1).checkHealth();
- if (message != null) errln(message);
- }
- }
-
- @Test
- public void testSwitchStorage() {
- FormatQuantity4 fq = new FormatQuantity4();
-
- fq.setToLong(1234123412341234L);
- assertFalse("Should not be using byte array", fq.usingBytes());
- assertBigDecimalEquals("Failed on initialize", "1234123412341234", fq.toBigDecimal());
- assertNull("Failed health check", fq.checkHealth());
- // Long -> Bytes
- fq.appendDigit((byte) 5, 0, true);
- assertTrue("Should be using byte array", fq.usingBytes());
- assertBigDecimalEquals("Failed on multiply", "12341234123412345", fq.toBigDecimal());
- assertNull("Failed health check", fq.checkHealth());
- // Bytes -> Long
- fq.roundToMagnitude(5, MATH_CONTEXT_HALF_EVEN);
- assertFalse("Should not be using byte array", fq.usingBytes());
- assertBigDecimalEquals("Failed on round", "12341234123400000", fq.toBigDecimal());
- assertNull("Failed health check", fq.checkHealth());
- }
-
- @Test
- public void testAppend() {
- FormatQuantity4 fq = new FormatQuantity4();
- fq.appendDigit((byte) 1, 0, true);
- assertBigDecimalEquals("Failed on append", "1.", fq.toBigDecimal());
- assertNull("Failed health check", fq.checkHealth());
- fq.appendDigit((byte) 2, 0, true);
- assertBigDecimalEquals("Failed on append", "12.", fq.toBigDecimal());
- assertNull("Failed health check", fq.checkHealth());
- fq.appendDigit((byte) 3, 1, true);
- assertBigDecimalEquals("Failed on append", "1203.", fq.toBigDecimal());
- assertNull("Failed health check", fq.checkHealth());
- fq.appendDigit((byte) 0, 1, true);
- assertBigDecimalEquals("Failed on append", "120300.", fq.toBigDecimal());
- assertNull("Failed health check", fq.checkHealth());
- fq.appendDigit((byte) 4, 0, true);
- assertBigDecimalEquals("Failed on append", "1203004.", fq.toBigDecimal());
- assertNull("Failed health check", fq.checkHealth());
- fq.appendDigit((byte) 0, 0, true);
- assertBigDecimalEquals("Failed on append", "12030040.", fq.toBigDecimal());
- assertNull("Failed health check", fq.checkHealth());
- fq.appendDigit((byte) 5, 0, false);
- assertBigDecimalEquals("Failed on append", "12030040.5", fq.toBigDecimal());
- assertNull("Failed health check", fq.checkHealth());
- fq.appendDigit((byte) 6, 0, false);
- assertBigDecimalEquals("Failed on append", "12030040.56", fq.toBigDecimal());
- assertNull("Failed health check", fq.checkHealth());
- fq.appendDigit((byte) 7, 3, false);
- assertBigDecimalEquals("Failed on append", "12030040.560007", fq.toBigDecimal());
- assertNull("Failed health check", fq.checkHealth());
- StringBuilder expected = new StringBuilder("12030040.560007");
- for (int i = 0; i < 10; i++) {
- fq.appendDigit((byte) 8, 0, false);
- expected.append("8");
- assertBigDecimalEquals("Failed on append", expected.toString(), fq.toBigDecimal());
- assertNull("Failed health check", fq.checkHealth());
- }
- }
-
- @Test
- public void testConvertToAccurateDouble() {
- // based on https://github.com/google/double-conversion/issues/28
- double[] hardDoubles = {
- 1651087494906221570.0,
- -5074790912492772E-327,
- 83602530019752571E-327,
- 2.207817077636718750000000000000,
- 1.818351745605468750000000000000,
- 3.941719055175781250000000000000,
- 3.738609313964843750000000000000,
- 3.967735290527343750000000000000,
- 1.328025817871093750000000000000,
- 3.920967102050781250000000000000,
- 1.015235900878906250000000000000,
- 1.335227966308593750000000000000,
- 1.344520568847656250000000000000,
- 2.879127502441406250000000000000,
- 3.695838928222656250000000000000,
- 1.845344543457031250000000000000,
- 3.793952941894531250000000000000,
- 3.211402893066406250000000000000,
- 2.565971374511718750000000000000,
- 0.965156555175781250000000000000,
- 2.700004577636718750000000000000,
- 0.767097473144531250000000000000,
- 1.780448913574218750000000000000,
- 2.624839782714843750000000000000,
- 1.305290222167968750000000000000,
- 3.834922790527343750000000000000,
- };
-
- double[] integerDoubles = {
- 51423,
- 51423e10,
- 4.503599627370496E15,
- 6.789512076111555E15,
- 9.007199254740991E15,
- 9.007199254740992E15
- };
-
- for (double d : hardDoubles) {
- checkDoubleBehavior(d, true, "");
- }
-
- for (double d : integerDoubles) {
- checkDoubleBehavior(d, false, "");
- }
-
- assertEquals("NaN check failed", Double.NaN, new FormatQuantity4(Double.NaN).toDouble());
- assertEquals(
- "Inf check failed",
- Double.POSITIVE_INFINITY,
- new FormatQuantity4(Double.POSITIVE_INFINITY).toDouble());
- assertEquals(
- "-Inf check failed",
- Double.NEGATIVE_INFINITY,
- new FormatQuantity4(Double.NEGATIVE_INFINITY).toDouble());
-
- // Generate random doubles
- String alert = "UNEXPECTED FAILURE: PLEASE REPORT THIS MESSAGE TO THE ICU TEAM: ";
- for (int i = 0; i < 1000000; i++) {
- double d = Double.longBitsToDouble(ThreadLocalRandom.current().nextLong());
- if (Double.isNaN(d) || Double.isInfinite(d)) continue;
- checkDoubleBehavior(d, false, alert);
- }
- }
-
- private static void checkDoubleBehavior(double d, boolean explicitRequired, String alert) {
- FormatQuantity4 fq = new FormatQuantity4(d);
- if (explicitRequired)
- assertTrue(alert + "Should be using approximate double", !fq.explicitExactDouble);
- assertEquals(alert + "Initial construction from hard double", d, fq.toDouble());
- fq.roundToInfinity();
- if (explicitRequired)
- assertTrue(alert + "Should not be using approximate double", fq.explicitExactDouble);
- assertDoubleEquals(alert + "After conversion to exact BCD (double)", d, fq.toDouble());
- assertBigDecimalEquals(
- alert + "After conversion to exact BCD (BigDecimal)",
- new BigDecimal(Double.toString(d)),
- fq.toBigDecimal());
- }
-
- @Test
- public void testUseApproximateDoubleWhenAble() {
- Object[][] cases = {
- {1.2345678, 1, MATH_CONTEXT_HALF_EVEN, false},
- {1.2345678, 7, MATH_CONTEXT_HALF_EVEN, false},
- {1.2345678, 12, MATH_CONTEXT_HALF_EVEN, false},
- {1.2345678, 13, MATH_CONTEXT_HALF_EVEN, true},
- {1.235, 1, MATH_CONTEXT_HALF_EVEN, false},
- {1.235, 2, MATH_CONTEXT_HALF_EVEN, true},
- {1.235, 3, MATH_CONTEXT_HALF_EVEN, false},
- {1.000000000000001, 0, MATH_CONTEXT_HALF_EVEN, false},
- {1.000000000000001, 0, MATH_CONTEXT_CEILING, true},
- {1.235, 1, MATH_CONTEXT_CEILING, false},
- {1.235, 2, MATH_CONTEXT_CEILING, false},
- {1.235, 3, MATH_CONTEXT_CEILING, true}
- };
-
- for (Object[] cas : cases) {
- double d = (Double) cas[0];
- int maxFrac = (Integer) cas[1];
- MathContext mc = (MathContext) cas[2];
- boolean usesExact = (Boolean) cas[3];
-
- FormatQuantity4 fq = new FormatQuantity4(d);
- assertTrue("Should be using approximate double", !fq.explicitExactDouble);
- fq.roundToMagnitude(-maxFrac, mc);
- assertEquals(
- "Using approximate double after rounding: " + d + " maxFrac=" + maxFrac + " " + mc,
- usesExact,
- fq.explicitExactDouble);
- }
- }
-
- static void assertDoubleEquals(String message, double d1, double d2) {
- boolean equal = (Math.abs(d1 - d2) < 1e-6) || (Math.abs((d1 - d2) / d1) < 1e-6);
- handleAssert(equal, message, d1, d2, null, false);
- }
-
- static void assertBigDecimalEquals(String message, String d1, BigDecimal d2) {
- assertBigDecimalEquals(message, new BigDecimal(d1), d2);
- }
-
- static void assertBigDecimalEquals(String message, BigDecimal d1, BigDecimal d2) {
- boolean equal = d1.compareTo(d2) == 0;
- handleAssert(equal, message, d1, d2, null, false);
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.dev.test.numbers;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import java.text.FieldPosition;
-import java.text.Format.Field;
-
-import org.junit.Test;
-
-import com.ibm.icu.impl.number.NumberStringBuilder;
-import com.ibm.icu.text.NumberFormat;
-
-/** @author sffc */
-public class NumberStringBuilderTest {
- private static final String[] EXAMPLE_STRINGS = {
- "",
- "xyz",
- "The quick brown fox jumps over the lazy dog",
- "😁",
- "mixed 😇 and ASCII",
- "with combining characters like 🇦🇧🇨🇩"
- };
-
- @Test
- public void testInsertAppendCharSequence() {
-
- StringBuilder sb1 = new StringBuilder();
- NumberStringBuilder sb2 = new NumberStringBuilder();
- for (String str : EXAMPLE_STRINGS) {
- NumberStringBuilder sb3 = new NumberStringBuilder();
- sb1.append(str);
- sb2.append(str, null);
- sb3.append(str, null);
- assertCharSequenceEquals(sb1, sb2);
- assertCharSequenceEquals(sb3, str);
-
- StringBuilder sb4 = new StringBuilder();
- NumberStringBuilder sb5 = new NumberStringBuilder();
- sb4.append("😇");
- sb4.append(str);
- sb4.append("xx");
- sb5.append("😇xx", null);
- sb5.insert(2, str, null);
- assertCharSequenceEquals(sb4, sb5);
-
- int start = Math.min(1, str.length());
- int end = Math.min(10, str.length());
- sb4.insert(3, str, start, end);
- sb5.insert(3, str, start, end, null);
- assertCharSequenceEquals(sb4, sb5);
-
- sb4.append(str.toCharArray());
- sb5.append(str.toCharArray(), null);
- assertCharSequenceEquals(sb4, sb5);
-
- sb4.insert(4, str.toCharArray());
- sb5.insert(4, str.toCharArray(), null);
- assertCharSequenceEquals(sb4, sb5);
- }
- }
-
- @Test
- public void testInsertAppendCodePoint() {
- int[] cases = {0, 1, 60, 127, 128, 0x7fff, 0x8000, 0xffff, 0x10000, 0x1f000, 0x10ffff};
-
- StringBuilder sb1 = new StringBuilder();
- NumberStringBuilder sb2 = new NumberStringBuilder();
- for (int cas : cases) {
- NumberStringBuilder sb3 = new NumberStringBuilder();
- sb1.appendCodePoint(cas);
- sb2.appendCodePoint(cas, null);
- sb3.appendCodePoint(cas, null);
- assertCharSequenceEquals(sb1, sb2);
- assertEquals(Character.codePointAt(sb3, 0), cas);
-
- StringBuilder sb4 = new StringBuilder();
- NumberStringBuilder sb5 = new NumberStringBuilder();
- sb4.append("😇");
- sb4.appendCodePoint(cas); // Java StringBuilder has no insertCodePoint()
- sb4.append("xx");
- sb5.append("😇xx", null);
- sb5.insertCodePoint(2, cas, null);
- assertCharSequenceEquals(sb4, sb5);
- }
- }
-
- @Test
- public void testFields() {
- for (String str : EXAMPLE_STRINGS) {
- NumberStringBuilder sb = new NumberStringBuilder();
- sb.append(str, null);
- sb.append(str, NumberFormat.Field.CURRENCY);
- Field[] fields = sb.toFieldArray();
- assertEquals(str.length() * 2, fields.length);
- for (int i = 0; i < str.length(); i++) {
- assertEquals(null, fields[i]);
- assertEquals(NumberFormat.Field.CURRENCY, fields[i + str.length()]);
- }
-
- // Very basic FieldPosition test. More robust tests happen in NumberFormatTest.
- // Let NumberFormatTest also take care of AttributedCharacterIterator material.
- FieldPosition fp = new FieldPosition(NumberFormat.Field.CURRENCY);
- sb.populateFieldPosition(fp, 0);
- assertEquals(str.length(), fp.getBeginIndex());
- assertEquals(str.length() * 2, fp.getEndIndex());
-
- if (str.length() > 0) {
- sb.insertCodePoint(2, 100, NumberFormat.Field.INTEGER);
- fields = sb.toFieldArray();
- assertEquals(str.length() * 2 + 1, fields.length);
- assertEquals(fields[2], NumberFormat.Field.INTEGER);
- }
-
- sb.append(sb.clone());
- sb.append(sb.toCharArray(), sb.toFieldArray());
- int numNull = 0;
- int numCurr = 0;
- int numInt = 0;
- Field[] oldFields = fields;
- fields = sb.toFieldArray();
- for (int i = 0; i < sb.length(); i++) {
- assertEquals(oldFields[i % oldFields.length], fields[i]);
- if (fields[i] == null) numNull++;
- else if (fields[i] == NumberFormat.Field.CURRENCY) numCurr++;
- else if (fields[i] == NumberFormat.Field.INTEGER) numInt++;
- else throw new AssertionError("Encountered unknown field in " + str);
- }
- assertEquals(str.length() * 4, numNull);
- assertEquals(numNull, numCurr);
- assertEquals(str.length() > 0 ? 4 : 0, numInt);
-
- NumberStringBuilder sb2 = new NumberStringBuilder();
- sb2.append(sb);
- assertTrue(sb.contentEquals(sb2));
- assertTrue(sb.contentEquals(sb2.toCharArray(), sb2.toFieldArray()));
-
- sb2.insertCodePoint(0, 50, NumberFormat.Field.FRACTION);
- assertTrue(!sb.contentEquals(sb2));
- assertTrue(!sb.contentEquals(sb2.toCharArray(), sb2.toFieldArray()));
- }
- }
-
- @Test
- public void testUnlimitedCapacity() {
- NumberStringBuilder builder = new NumberStringBuilder();
- // The builder should never fail upon repeated appends.
- for (int i = 0; i < 1000; i++) {
- assertEquals(builder.length(), i);
- builder.appendCodePoint('x', null);
- assertEquals(builder.length(), i + 1);
- }
- }
-
- private static void assertCharSequenceEquals(CharSequence a, CharSequence b) {
- assertEquals(a.toString(), b.toString());
-
- assertEquals(a.length(), b.length());
- for (int i = 0; i < a.length(); i++) {
- assertEquals(a.charAt(i), b.charAt(i));
- }
-
- int start = Math.min(2, a.length());
- int end = Math.min(12, a.length());
- if (start != end) {
- assertCharSequenceEquals(a.subSequence(start, end), b.subSequence(start, end));
- }
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.dev.test.numbers;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import org.junit.Test;
-
-import com.ibm.icu.impl.number.PatternString;
-import com.ibm.icu.impl.number.Properties;
-import com.ibm.icu.text.DecimalFormatSymbols;
-import com.ibm.icu.util.ULocale;
-
-/** @author sffc */
-public class PatternStringTest {
-
- @Test
- public void testLocalized() {
- DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(ULocale.ENGLISH);
- symbols.setDecimalSeparatorString("a");
- symbols.setPercentString("b");
- symbols.setMinusSignString(".");
- symbols.setPlusSignString("'");
-
- String standard = "+-abcb''a''#,##0.0%'a%'";
- String localized = "’.'ab'c'b''a'''#,##0a0b'a%'";
- String toStandard = "+-'ab'c'b''a'''#,##0.0%'a%'";
-
- assertEquals(localized, PatternString.convertLocalized(standard, symbols, true));
- assertEquals(toStandard, PatternString.convertLocalized(localized, symbols, false));
- }
-
- @Test
- public void testToPatternSimple() {
- String[][] cases = {
- {"#", "0"},
- {"0", "0"},
- {"#0", "0"},
- {"###", "0"},
- {"0.##", "0.##"},
- {"0.00", "0.00"},
- {"0.00#", "0.00#"},
- {"#E0", "#E0"},
- {"0E0", "0E0"},
- {"#00E00", "#00E00"},
- {"#,##0", "#,##0"},
- {"#,##0E0", "#,##0E0"},
- {"#;#", "0;0"},
- {"#;-#", "0"}, // ignore a negative prefix pattern of '-' since that is the default
- {"**##0", "**##0"},
- {"*'x'##0", "*x##0"},
- {"a''b0", "a''b0"},
- {"*''##0", "*''##0"},
- {"*📺##0", "*'📺'##0"},
- {"*'நி'##0", "*'நி'##0"},
- };
-
- for (String[] cas : cases) {
- String input = cas[0];
- String output = cas[1];
-
- Properties properties = PatternString.parseToProperties(input);
- String actual = PatternString.propertiesToString(properties);
- assertEquals(
- "Failed on input pattern '" + input + "', properties " + properties, output, actual);
- }
- }
-
- @Test
- public void testToPatternWithProperties() {
- Object[][] cases = {
- {new Properties().setPositivePrefix("abc"), "abc#"},
- {new Properties().setPositiveSuffix("abc"), "#abc"},
- {new Properties().setPositivePrefixPattern("abc"), "abc#"},
- {new Properties().setPositiveSuffixPattern("abc"), "#abc"},
- {new Properties().setNegativePrefix("abc"), "#;abc#"},
- {new Properties().setNegativeSuffix("abc"), "#;#abc"},
- {new Properties().setNegativePrefixPattern("abc"), "#;abc#"},
- {new Properties().setNegativeSuffixPattern("abc"), "#;#abc"},
- {new Properties().setPositivePrefix("+"), "'+'#"},
- {new Properties().setPositivePrefixPattern("+"), "+#"},
- {new Properties().setPositivePrefix("+'"), "'+'''#"},
- {new Properties().setPositivePrefix("'+"), "'''+'#"},
- {new Properties().setPositivePrefix("'"), "''#"},
- {new Properties().setPositivePrefixPattern("+''"), "+''#"},
- };
-
- for (Object[] cas : cases) {
- Properties input = (Properties) cas[0];
- String output = (String) cas[1];
-
- String actual = PatternString.propertiesToString(input);
- assertEquals("Failed on input properties " + input, output, actual);
- }
- }
-
- @Test
- public void testExceptionOnInvalid() {
- String[] invalidPatterns = {"#.#.#", "0#", "0#.", ".#0", "0#.#0", "@0", "0@"};
-
- for (String pattern : invalidPatterns) {
- try {
- PatternString.parseToProperties(pattern);
- fail("Didn't throw IllegalArgumentException when parsing pattern: " + pattern);
- } catch (IllegalArgumentException e) {
- }
- }
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.dev.test.numbers;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.lang.reflect.Parameter;
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.math.MathContext;
-import java.math.RoundingMode;
-import java.util.HashSet;
-import java.util.Set;
-
-import org.junit.Test;
-
-import com.ibm.icu.dev.test.serializable.SerializableTestUtility;
-import com.ibm.icu.impl.number.Parse.ParseMode;
-import com.ibm.icu.impl.number.PatternString;
-import com.ibm.icu.impl.number.Properties;
-import com.ibm.icu.impl.number.formatters.CurrencyFormat.CurrencyStyle;
-import com.ibm.icu.impl.number.formatters.PaddingFormat.PadPosition;
-import com.ibm.icu.impl.number.rounders.SignificantDigitsRounder.SignificantDigitsMode;
-import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
-import com.ibm.icu.text.CurrencyPluralInfo;
-import com.ibm.icu.text.MeasureFormat.FormatWidth;
-import com.ibm.icu.util.Currency;
-import com.ibm.icu.util.Currency.CurrencyUsage;
-import com.ibm.icu.util.MeasureUnit;
-import com.ibm.icu.util.ULocale;
-
-public class PropertiesTest {
-
- @Test
- public void testBasicEquals() {
- Properties p1 = new Properties();
- Properties p2 = new Properties();
- assertEquals(p1, p2);
-
- p1.setPositivePrefix("abc");
- assertNotEquals(p1, p2);
- p2.setPositivePrefix("xyz");
- assertNotEquals(p1, p2);
- p1.setPositivePrefix("xyz");
- assertEquals(p1, p2);
- }
-
- @Test
- public void testFieldCoverage() {
- Properties p0 = new Properties();
- Properties p1 = new Properties();
- Properties p2 = new Properties();
- Properties p3 = new Properties();
- Properties p4 = new Properties();
-
- Set<Integer> hashCodes = new HashSet<Integer>();
- Field[] fields = Properties.class.getDeclaredFields();
- for (Field field : fields) {
- if (Modifier.isStatic(field.getModifiers())) {
- continue;
- }
-
- // Check for getters and setters
- String fieldNamePascalCase =
- Character.toUpperCase(field.getName().charAt(0)) + field.getName().substring(1);
- String getterName = "get" + fieldNamePascalCase;
- String setterName = "set" + fieldNamePascalCase;
- Method getter, setter;
- try {
- getter = Properties.class.getMethod(getterName);
- assertEquals(
- "Getter does not return correct type", field.getType(), getter.getReturnType());
- } catch (NoSuchMethodException e) {
- fail("Could not find method " + getterName + " for field " + field);
- continue;
- } catch (SecurityException e) {
- fail("Could not access method " + getterName + " for field " + field);
- continue;
- }
- try {
- setter = Properties.class.getMethod(setterName, field.getType());
- assertEquals(
- "Method " + setterName + " does not return correct type",
- Properties.class,
- setter.getReturnType());
- } catch (NoSuchMethodException e) {
- fail("Could not find method " + setterName + " for field " + field);
- continue;
- } catch (SecurityException e) {
- fail("Could not access method " + setterName + " for field " + field);
- continue;
- }
-
- // Check for parameter name equality.
- // The parameter name is not always available, depending on compiler settings.
- Parameter param = setter.getParameters()[0];
- if (!param.getName().subSequence(0, 3).equals("arg")) {
- assertEquals("Parameter name should equal field name", field.getName(), param.getName());
- }
-
- try {
- // Check for default value (should be null for objects)
- if (field.getType() != Integer.TYPE && field.getType() != Boolean.TYPE) {
- Object default0 = getter.invoke(p0);
- assertEquals("Field " + field + " has non-null default value:", null, default0);
- }
-
- // Check for getter, equals, and hash code behavior
- Object val0 = getSampleValueForType(field.getType(), 0);
- Object val1 = getSampleValueForType(field.getType(), 1);
- Object val2 = getSampleValueForType(field.getType(), 2);
- assertNotEquals(val0, val1);
- setter.invoke(p1, val0);
- setter.invoke(p2, val0);
- assertEquals(p1, p2);
- assertEquals(p1.hashCode(), p2.hashCode());
- assertEquals(getter.invoke(p1), getter.invoke(p2));
- assertEquals(getter.invoke(p1), val0);
- assertNotEquals(getter.invoke(p1), val1);
- hashCodes.add(p1.hashCode());
- setter.invoke(p1, val1);
- assertNotEquals("Field " + field + " is missing from equals()", p1, p2);
- assertNotEquals(getter.invoke(p1), getter.invoke(p2));
- assertNotEquals(getter.invoke(p1), val0);
- assertEquals(getter.invoke(p1), val1);
- setter.invoke(p1, val0);
- assertEquals("Field " + field + " setter might have side effects", p1, p2);
- assertEquals(p1.hashCode(), p2.hashCode());
- assertEquals(getter.invoke(p1), getter.invoke(p2));
- setter.invoke(p1, val1);
- setter.invoke(p2, val1);
- assertEquals(p1, p2);
- assertEquals(p1.hashCode(), p2.hashCode());
- assertEquals(getter.invoke(p1), getter.invoke(p2));
- setter.invoke(p1, val2);
- setter.invoke(p1, val1);
- assertEquals("Field " + field + " setter might have side effects", p1, p2);
- assertEquals(p1.hashCode(), p2.hashCode());
- assertEquals(getter.invoke(p1), getter.invoke(p2));
- hashCodes.add(p1.hashCode());
-
- // Check for clone behavior
- Properties copy = p1.clone();
- assertEquals("Field " + field + " did not get copied in clone", p1, copy);
- assertEquals(p1.hashCode(), copy.hashCode());
- assertEquals(getter.invoke(p1), getter.invoke(copy));
-
- // Check for copyFrom behavior
- setter.invoke(p1, val0);
- assertNotEquals(p1, p2);
- assertNotEquals(getter.invoke(p1), getter.invoke(p2));
- p2.copyFrom(p1);
- assertEquals("Field " + field + " is missing from copyFrom()", p1, p2);
- assertEquals(p1.hashCode(), p2.hashCode());
- assertEquals(getter.invoke(p1), getter.invoke(p2));
-
- // Load values into p3 and p4 for clear() behavior test
- setter.invoke(p3, getSampleValueForType(field.getType(), 3));
- hashCodes.add(p3.hashCode());
- setter.invoke(p4, getSampleValueForType(field.getType(), 4));
- hashCodes.add(p4.hashCode());
- } catch (IllegalAccessException e) {
- fail("Could not access method for field " + field);
- } catch (IllegalArgumentException e) {
- fail("Could call method for field " + field);
- } catch (InvocationTargetException e) {
- fail("Could invoke method on target for field " + field);
- }
- }
-
- // Check for clear() behavior
- assertNotEquals(p3, p4);
- p3.clear();
- p4.clear();
- assertEquals("A field is missing from the clear() function", p3, p4);
-
- // A good hashCode() implementation should produce very few collisions. We added at most
- // 4*fields.length codes to the set. We'll say the implementation is good if we had at least
- // fields.length unique values.
- // TODO: Should the requirement be stronger than this?
- assertTrue(
- "Too many hash code collisions: " + hashCodes.size() + " out of " + (fields.length * 4),
- hashCodes.size() >= fields.length);
- }
-
- /**
- * Creates a valid sample instance of the given type. Used to simulate getters and setters.
- *
- * @param type The type to generate.
- * @param seed An integer seed, guaranteed to be positive. The same seed should generate two
- * instances that are equal. A different seed should in general generate two instances that
- * are not equal; this might not always be possible, such as with booleans or enums where
- * there are limited possible values.
- * @return An instance of the specified type.
- */
- Object getSampleValueForType(Class<?> type, int seed) {
- if (type == Integer.TYPE) {
- return seed * 1000001;
-
- } else if (type == Boolean.TYPE) {
- return (seed % 2) == 0;
-
- } else if (type == BigDecimal.class) {
- if (seed == 0) return null;
- return new BigDecimal(seed * 1000002);
-
- } else if (type == String.class) {
- if (seed == 0) return null;
- return BigInteger.valueOf(seed * 1000003).toString(32);
-
- } else if (type == CompactStyle.class) {
- if (seed == 0) return null;
- CompactStyle[] values = CompactStyle.values();
- return values[seed % values.length];
-
- } else if (type == Currency.class) {
- if (seed == 0) return null;
- Object[] currencies = Currency.getAvailableCurrencies().toArray();
- return currencies[seed % currencies.length];
-
- } else if (type == CurrencyPluralInfo.class) {
- if (seed == 0) return null;
- ULocale[] locales = ULocale.getAvailableLocales();
- return CurrencyPluralInfo.getInstance(locales[seed % locales.length]);
-
- } else if (type == CurrencyStyle.class) {
- if (seed == 0) return null;
- CurrencyStyle[] values = CurrencyStyle.values();
- return values[seed % values.length];
-
- } else if (type == CurrencyUsage.class) {
- if (seed == 0) return null;
- CurrencyUsage[] values = CurrencyUsage.values();
- return values[seed % values.length];
-
- } else if (type == FormatWidth.class) {
- if (seed == 0) return null;
- FormatWidth[] values = FormatWidth.values();
- return values[seed % values.length];
-
- } else if (type == MathContext.class) {
- if (seed == 0) return null;
- RoundingMode[] modes = RoundingMode.values();
- return new MathContext(seed, modes[seed % modes.length]);
-
- } else if (type == MeasureUnit.class) {
- if (seed == 0) return null;
- Object[] units = MeasureUnit.getAvailable().toArray();
- return units[seed % units.length];
-
- } else if (type == PadPosition.class) {
- if (seed == 0) return null;
- PadPosition[] values = PadPosition.values();
- return values[seed % values.length];
-
- } else if (type == ParseMode.class) {
- if (seed == 0) return null;
- ParseMode[] values = ParseMode.values();
- return values[seed % values.length];
-
- } else if (type == RoundingMode.class) {
- if (seed == 0) return null;
- RoundingMode[] values = RoundingMode.values();
- return values[seed % values.length];
-
- } else if (type == SignificantDigitsMode.class) {
- if (seed == 0) return null;
- SignificantDigitsMode[] values = SignificantDigitsMode.values();
- return values[seed % values.length];
-
- } else {
- fail("Don't know how to handle type " + type + ". Please add it to getSampleValueForType().");
- return null;
- }
- }
-
- @Test
- public void TestBasicSerializationRoundTrip() throws IOException, ClassNotFoundException {
- Properties props0 = new Properties();
-
- // Write values to some of the fields
- PatternString.parseToExistingProperties("A-**####,#00.00#b¤", props0);
-
- // Write to byte stream
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- ObjectOutputStream oos = new ObjectOutputStream(baos);
- oos.writeObject(props0);
- oos.flush();
- baos.close();
- byte[] bytes = baos.toByteArray();
-
- // Read from byte stream
- ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
- Object obj = ois.readObject();
- ois.close();
- Properties props1 = (Properties) obj;
-
- // Test equality
- assertEquals("Did not round-trip through serialization", props0, props1);
- }
-
- /** Handler for serialization compatibility test suite. */
- public static class PropertiesHandler implements SerializableTestUtility.Handler {
-
- @Override
- public Object[] getTestObjects() {
- return new Object[] {
- new Properties(),
- PatternString.parseToProperties("x#,##0.00%"),
- new Properties().setCompactStyle(CompactStyle.LONG).setMinimumExponentDigits(2)
- };
- }
-
- @Override
- public boolean hasSameBehavior(Object a, Object b) {
- return a.equals(b);
- }
- }
-}
+++ /dev/null
-// © 2017 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html#License
-package com.ibm.icu.dev.test.numbers;
-
-import static org.junit.Assert.assertEquals;
-
-import org.junit.Test;
-
-import com.ibm.icu.impl.number.FormatQuantity;
-import com.ibm.icu.impl.number.FormatQuantity4;
-import com.ibm.icu.impl.number.Properties;
-import com.ibm.icu.impl.number.rounders.SignificantDigitsRounder;
-import com.ibm.icu.impl.number.rounders.SignificantDigitsRounder.SignificantDigitsMode;
-
-public class RounderTest {
-
- @Test
- public void testSignificantDigitsRounder() {
- Object[][][][] cases = {
- {
- {{1, -1}, {0, 2}, {2, 4}}, // minInt, maxInt, minFrac, maxFrac, minSig, maxSig
- {
- {0.0, "0.0", "0.0", "0"},
- {0.054321, "0.05432", "0.05", "0.054"},
- {0.54321, "0.5432", "0.54", "0.54"},
- {1.0, "1.0", "1.0", "1"},
- {5.4321, "5.432", "5.43", "5.43"},
- {10.0, "10", "10", "10"},
- {11.0, "11", "11", "11"},
- {100.0, "100", "100", "100"},
- {100.23, "100.2", "100.2", "100.2"},
- {543210.0, "543200", "543200", "543200"},
- }
- },
- {
- {{1, -1}, {0, 0}, {2, -1}}, // minInt, maxInt, minFrac, maxFrac, minSig, maxSig
- {
- {0.0, "0.0", "0", "0"},
- {0.054321, "0.054321", "0", "0.054"},
- {0.54321, "0.54321", "1", "0.54"},
- {1.0, "1.0", "1", "1"},
- {5.4321, "5.4321", "5", "5.4"},
- {10.0, "10", "10", "10"},
- {11.0, "11", "11", "11"},
- {100.0, "100", "100", "100"},
- {100.23, "100.23", "100", "100"},
- {543210.0, "543210", "543210", "543210"},
- }
- },
- {
- {{0, 2}, {1, 2}, {3, 3}}, // minInt, maxInt, minFrac, maxFrac, minSig, maxSig
- {
- {0.0, ".000", ".00", ".0"},
- {0.054321, ".0543", ".05", ".0543"},
- {0.54321, ".543", ".54", ".543"},
- {1.0, "1.00", "1.00", "1.0"},
- {5.4321, "5.43", "5.43", "5.43"},
- {10.0, "10.0", "10.0", "10.0"},
- {11.0, "11.0", "11.0", "11.0"},
- {100.0, "00.0", "00.0", "00.0"},
- {100.23, "00.2", "00.2", "00.2"},
- {543210.0, "10.0", "10.0", "10.0"}
- }
- }
- };
-
- int caseNumber = 0;
- for (Object[][][] cas : cases) {
- int minInt = (Integer) cas[0][0][0];
- int maxInt = (Integer) cas[0][0][1];
- int minFrac = (Integer) cas[0][1][0];
- int maxFrac = (Integer) cas[0][1][1];
- int minSig = (Integer) cas[0][2][0];
- int maxSig = (Integer) cas[0][2][1];
-
- Properties properties = new Properties();
- FormatQuantity4 fq = new FormatQuantity4();
- properties.setMinimumIntegerDigits(minInt);
- properties.setMaximumIntegerDigits(maxInt);
- properties.setMinimumFractionDigits(minFrac);
- properties.setMaximumFractionDigits(maxFrac);
- properties.setMinimumSignificantDigits(minSig);
- properties.setMaximumSignificantDigits(maxSig);
-
- int runNumber = 0;
- for (Object[] run : cas[1]) {
- double input = (Double) run[0];
- String expected1 = (String) run[1];
- String expected2 = (String) run[2];
- String expected3 = (String) run[3];
-
- properties.setSignificantDigitsMode(SignificantDigitsMode.OVERRIDE_MAXIMUM_FRACTION);
- fq.setToDouble(input);
- SignificantDigitsRounder.getInstance(properties).apply(fq);
- assertEquals(
- "Case " + caseNumber + ", run " + runNumber + ", mode 0: " + fq,
- expected1,
- formatQuantityToString(fq));
-
- properties.setSignificantDigitsMode(SignificantDigitsMode.RESPECT_MAXIMUM_FRACTION);
- fq.setToDouble(input);
- SignificantDigitsRounder.getInstance(properties).apply(fq);
- assertEquals(
- "Case " + caseNumber + ", run " + runNumber + ", mode 1: " + fq,
- expected2,
- formatQuantityToString(fq));
-
- properties.setSignificantDigitsMode(SignificantDigitsMode.ENSURE_MINIMUM_SIGNIFICANT);
- fq.setToDouble(input);
- SignificantDigitsRounder.getInstance(properties).apply(fq);
- assertEquals(
- "Case " + caseNumber + ", run " + runNumber + ", mode 2: " + fq,
- expected3,
- formatQuantityToString(fq));
-
- runNumber++;
- }
-
- caseNumber++;
- }
- }
-
- private String formatQuantityToString(FormatQuantity fq) {
- StringBuilder sb = new StringBuilder();
- int udm = fq.getUpperDisplayMagnitude();
- int ldm = fq.getLowerDisplayMagnitude();
- if (udm == -1) sb.append('.');
- for (int m = udm; m >= ldm; m--) {
- sb.append(fq.getDigit(m));
- if (m == 0 && m > ldm) sb.append('.');
- }
- return sb.toString();
- }
-}
NumberFormat format_b = (NumberFormat) b;
double number = 1234.56;
- String result_a = format_a.format(number);
- String result_b = format_b.format(number);
- boolean equal = result_a.equals(result_b);
- if (!equal) {
- System.out.println(format_a+" "+format_b);
- System.out.println(result_a+" "+result_b);
- }
- return equal;
+ return format_a.format(number).equals(format_b.format(number));
}
}
char chars_a[] = getCharSymbols(dfs_a);
char chars_b[] = getCharSymbols(dfs_b);
- // Spot-check char-to-string conversion (ICU 58)
- String percent_a1 = Character.toString(dfs_a.getPercent());
- String percent_a2 = dfs_a.getPercentString();
- String percent_b1 = Character.toString(dfs_b.getPercent());
- String percent_b2 = dfs_b.getPercentString();
-
- return SerializableTestUtility.compareStrings(strings_a, strings_b)
- && SerializableTestUtility.compareChars(chars_a, chars_b)
- && percent_a1.equals(percent_b1)
- && percent_a2.equals(percent_b2)
- && percent_a1.equals(percent_a2);
+ return SerializableTestUtility.compareStrings(strings_a, strings_b) && SerializableTestUtility.compareChars(chars_a, chars_b);
}
}
import com.ibm.icu.dev.test.format.MeasureUnitTest;
import com.ibm.icu.dev.test.format.PluralRulesTest;
-import com.ibm.icu.dev.test.numbers.PropertiesTest;
import com.ibm.icu.impl.JavaTimeZone;
import com.ibm.icu.impl.OlsonTimeZone;
import com.ibm.icu.impl.TimeZoneAdapter;
map.put("com.ibm.icu.util.MeasureUnit", new MeasureUnitTest.MeasureUnitHandler());
map.put("com.ibm.icu.util.TimeUnit", new MeasureUnitTest.MeasureUnitHandler());
map.put("com.ibm.icu.text.MeasureFormat", new MeasureUnitTest.MeasureFormatHandler());
- map.put("com.ibm.icu.impl.number.Properties", new PropertiesTest.PropertiesHandler());
map.put("com.ibm.icu.util.ICUException", new ICUExceptionHandler());
map.put("com.ibm.icu.util.ICUUncheckedIOException", new ICUUncheckedIOExceptionHandler());
return;
}
- if (className.equals("com.ibm.icu.text.DecimalFormat_ICU58")) {
- // Do not test the legacy DecimalFormat class in ICU 59
- return;
- }
-
if (c.isEnum() || !serializable.isAssignableFrom(c)) {
//System.out.println("@@@ Skipping: " + className);
return;
*/
package com.ibm.icu.dev.test.util;
-import java.util.Arrays;
import java.util.Iterator;
import org.junit.Test;
private static final Integer FRI = new Integer(6);
private static final Integer SAT = new Integer(7);
- private static final Integer SUP1 = new Integer(8);
- private static final Integer SUP2 = new Integer(9);
- private static final Integer SUP3 = new Integer(10);
- private static final Integer SUP4 = new Integer(11);
-
private static final Integer FOO = new Integer(-1);
private static final Integer BAR = new Integer(-2);
-
+
private static final Object[][] TESTDATA = {
{"Sunday", SUN},
{"Monday", MON},
{"W", WED},
{"T", THU},
{"F", FRI},
- {"S", SAT},
- {"L📺", SUP1}, // L, 0xD83D, 0xDCFA
- {"L📺1", SUP2}, // L, 0xD83D, 0xDCFA, 1
- {"L📻", SUP3}, // L, 0xD83D, 0xDCFB
- {"L🃏", SUP4}, // L, 0xD83C, 0xDCCF
+ {"S", SAT}
};
private static final Object[][] TESTCASES = {
{"TEST", new Object[]{TUE, THU}, new Object[]{TUE, THU}},
{"SUN", new Object[]{SUN, SAT}, SUN},
{"super", null, SUN},
- {"NO", null, null},
- {"L📺", SUP1, SUP1},
- {"l📺", null, SUP1},
- };
-
- private static final Object[][] TESTCASES_PARSE = {
- {
- "Sunday",
- new Object[]{
- new Object[]{SAT,SUN}, new Object[]{SAT,SUN}, // matches on "S"
- null, null, // matches on "Su"
- SUN, SUN, // matches on "Sun"
- null, null, // matches on "Sund"
- null, null, // matches on "Sunda"
- SUN, SUN, // matches on "Sunday"
- }
- },
- {
- "sunday",
- new Object[]{
- null, new Object[]{SAT,SUN}, // matches on "s"
- null, null, // matches on "su"
- null, SUN, // matches on "sun"
- null, null, // matches on "sund"
- null, null, // matches on "sunda"
- null, SUN, // matches on "sunday"
- }
- },
- {
- "MMM",
- new Object[]{
- MON, MON, // matches on "M"
- // no more matches in data
- }
- },
- {
- "BBB",
- new Object[]{
- // no matches in data
- }
- },
- {
- "l📺12",
- new Object[]{
- null, null, // matches on "L"
- null, SUP1, // matches on "L📺"
- null, SUP2, // matches on "L📺1"
- // no more matches in data
- }
- },
- {
- "L📻",
- new Object[] {
- null, null, // matches on "L"
- SUP3, SUP3, // matches on "L📻"
- }
- },
- {
- "L🃏",
- new Object[] {
- null, null, // matches on "L"
- SUP4, SUP4, // matches on "L🃏"
- }
- }
+ {"NO", null, null}
};
@Test
logln("Test for get(String)");
for (int i = 0; i < TESTCASES.length; i++) {
itr = map.get((String)TESTCASES[i][0]);
- checkResult("get(String) case " + i, itr, TESTCASES[i][1]);
+ checkResult(itr, TESTCASES[i][1]);
}
logln("Test for get(String, int)");
}
textBuf.append(TESTCASES[i][0]);
itr = map.get(textBuf.toString(), i);
- checkResult("get(String, int) case " + i, itr, TESTCASES[i][1]);
- }
-
- logln("Test for ParseState");
- for (int i = 0; i < TESTCASES_PARSE.length; i++) {
- String test = (String) TESTCASES_PARSE[i][0];
- Object[] expecteds = (Object[]) TESTCASES_PARSE[i][1];
- checkParse(map, test, expecteds, true);
+ checkResult(itr, TESTCASES[i][1]);
}
// Add duplicated entry
// Make sure the all entries are returned
itr = map.get("Sunday");
- checkResult("Get Sunday", itr, new Object[]{FOO, SUN});
+ checkResult(itr, new Object[]{FOO, SUN});
}
@Test
logln("Test for get(String)");
for (int i = 0; i < TESTCASES.length; i++) {
itr = map.get((String)TESTCASES[i][0]);
- checkResult("get(String) case " + i, itr, TESTCASES[i][2]);
+ checkResult(itr, TESTCASES[i][2]);
}
-
+
logln("Test for get(String, int)");
StringBuffer textBuf = new StringBuffer();
for (int i = 0; i < TESTCASES.length; i++) {
}
textBuf.append(TESTCASES[i][0]);
itr = map.get(textBuf.toString(), i);
- checkResult("get(String, int) case " + i, itr, TESTCASES[i][2]);
- }
-
- logln("Test for ParseState");
- for (int i = 0; i < TESTCASES_PARSE.length; i++) {
- String test = (String) TESTCASES_PARSE[i][0];
- Object[] expecteds = (Object[]) TESTCASES_PARSE[i][1];
- checkParse(map, test, expecteds, false);
+ checkResult(itr, TESTCASES[i][2]);
}
// Add duplicated entry
// Make sure the all entries are returned
itr = map.get("Sunday");
- checkResult("Get Sunday", itr, new Object[]{SUN, FOO, BAR});
- }
-
- private void checkParse(TextTrieMap map, String text, Object[] rawExpecteds, boolean caseSensitive) {
- // rawExpecteds has even-valued indices for case sensitive and odd-valued indicies for case insensitive
- // Get out only the values that we want.
- Object[] expecteds = null;
- for (int i=rawExpecteds.length/2-1; i>=0; i--) {
- int j = i*2+(caseSensitive?0:1);
- if (rawExpecteds[j] != null) {
- if (expecteds == null) {
- expecteds = new Object[i+1];
- }
- expecteds[i] = rawExpecteds[j];
- }
- }
- if (expecteds == null) {
- expecteds = new Object[0];
- }
-
- TextTrieMap.ParseState state = null;
- for (int charOffset=0, cpOffset=0; charOffset < text.length(); cpOffset++) {
- int cp = Character.codePointAt(text, charOffset);
- if (state == null) {
- state = map.openParseState(cp);
- }
- if (state == null) {
- assertEquals("Expected matches, but no matches are available", 0, expecteds.length);
- break;
- }
- state.accept(cp);
- if (cpOffset < expecteds.length - 1) {
- assertFalse(
- "In middle of parse sequence, but atEnd() is true: '" + text + "' offset " + charOffset,
- state.atEnd());
- } else if (cpOffset == expecteds.length) {
- // Note: it possible for atEnd() to be either true or false at expecteds.length - 1;
- // if true, we are at the end of the input string; if false, there is still input string
- // left to be consumed, but we don't know if there are remaining matches.
- assertTrue(
- "At end of parse sequence, but atEnd() is false: '" + text + "' offset " + charOffset,
- state.atEnd());
- break;
- }
- Object expected = expecteds[cpOffset];
- Iterator actual = state.getCurrentMatches();
- checkResult("ParseState '" + text + "' offset " + charOffset, actual, expected);
- charOffset += Character.charCount(cp);
- }
+ checkResult(itr, new Object[]{SUN, FOO, BAR});
}
private boolean eql(Object o1, Object o2) {
return o1.equals(o2);
}
- private void checkResult(String memo, Iterator itr, Object expected) {
+ private void checkResult(Iterator itr, Object expected) {
if (itr == null) {
if (expected != null) {
- String expectedStr = (expected instanceof Object[])
- ? Arrays.toString((Object[]) expected)
- : expected.toString();
- errln("FAIL: Empty results: " + memo + ": Expected: " + expectedStr);
+ errln("FAIL: Empty results - Expected: " + expected);
}
return;
}