ICU-7467 Merging ICU4J decimal format overhaul from branch to trunk.
X-SVN-Rev: 39877
This commit is contained in:
parent
a59804f0ac
commit
dbcae565a2
1
.gitignore
vendored
1
.gitignore
vendored
@ -897,6 +897,7 @@ icu4j/.project
|
||||
icu4j/build-local.properties
|
||||
icu4j/demos/out
|
||||
icu4j/doc
|
||||
icu4j/eclipse
|
||||
icu4j/eclipse-build/out
|
||||
icu4j/lib/*.jar
|
||||
icu4j/main/classes/charset/out
|
||||
|
@ -105,6 +105,85 @@ public class TextTrieMap<V> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
@ -234,6 +313,21 @@ public class TextTrieMap<V> {
|
||||
_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;
|
||||
@ -272,6 +366,37 @@ public class TextTrieMap<V> {
|
||||
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);
|
||||
|
@ -0,0 +1,524 @@
|
||||
// © 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);
|
||||
}
|
||||
}
|
@ -0,0 +1,286 @@
|
||||
// © 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;
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
// © 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);
|
||||
}
|
277
icu4j/main/classes/core/src/com/ibm/icu/impl/number/Format.java
Normal file
277
icu4j/main/classes/core/src/com/ibm/icu/impl/number/Format.java
Normal file
@ -0,0 +1,277 @@
|
||||
// © 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);
|
||||
}
|
@ -0,0 +1,180 @@
|
||||
// © 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();
|
||||
}
|
@ -0,0 +1,856 @@
|
||||
// © 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.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
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;
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,173 @@
|
||||
// © 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);
|
||||
}
|
||||
}
|
@ -0,0 +1,222 @@
|
||||
// © 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);
|
||||
}
|
||||
}
|
@ -0,0 +1,407 @@
|
||||
// © 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);
|
||||
}
|
||||
}
|
@ -0,0 +1,900 @@
|
||||
// © 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();
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
// © 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());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,128 @@
|
||||
// © 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
// © 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;
|
||||
}
|
||||
}
|
@ -0,0 +1,411 @@
|
||||
// © 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;
|
||||
}
|
||||
}
|
@ -0,0 +1,296 @@
|
||||
// © 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;
|
||||
}
|
||||
}
|
2110
icu4j/main/classes/core/src/com/ibm/icu/impl/number/Parse.java
Normal file
2110
icu4j/main/classes/core/src/com/ibm/icu/impl/number/Parse.java
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,855 @@
|
||||
// © 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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,994 @@
|
||||
// © 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);
|
||||
}
|
||||
}
|
||||
}
|
265
icu4j/main/classes/core/src/com/ibm/icu/impl/number/Rounder.java
Normal file
265
icu4j/main/classes/core/src/com/ibm/icu/impl/number/Rounder.java
Normal file
@ -0,0 +1,265 @@
|
||||
// © 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#roundToIncrement}, {@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);
|
||||
}
|
||||
}
|
@ -0,0 +1,165 @@
|
||||
// © 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;
|
||||
}
|
||||
}
|
123
icu4j/main/classes/core/src/com/ibm/icu/impl/number/demo.java
Normal file
123
icu4j/main/classes/core/src/com/ibm/icu/impl/number/demo.java
Normal file
@ -0,0 +1,123 @@
|
||||
// © 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))));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
// © 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);
|
||||
}
|
||||
}
|
@ -0,0 +1,449 @@
|
||||
// © 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,299 @@
|
||||
// © 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
// © 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);
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
// © 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;
|
||||
}
|
||||
}
|
@ -0,0 +1,173 @@
|
||||
// © 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);
|
||||
}
|
||||
}
|
@ -0,0 +1,227 @@
|
||||
// © 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);
|
||||
}
|
||||
}
|
@ -0,0 +1,256 @@
|
||||
// © 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
// © 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;
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
// © 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,233 @@
|
||||
// © 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);
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
// © 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.
|
||||
}
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
// © 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();
|
||||
}
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
// © 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();
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
// © 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);
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
// © 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());
|
||||
}
|
||||
}
|
@ -0,0 +1,130 @@
|
||||
// © 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();
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
// © 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);
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
// © 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);
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
// © 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();
|
||||
}
|
||||
}
|
@ -0,0 +1,210 @@
|
||||
// © 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 {
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,524 +0,0 @@
|
||||
// © 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];
|
||||
}
|
||||
}
|
@ -18,44 +18,37 @@ import java.math.BigInteger;
|
||||
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.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.impl.number.FormatQuantity4;
|
||||
import com.ibm.icu.impl.number.Properties;
|
||||
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.
|
||||
* <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.
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
* <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.
|
||||
*
|
||||
* @author markdavis
|
||||
* @stable ICU 49
|
||||
@ -64,39 +57,29 @@ public class CompactDecimalFormat extends DecimalFormat {
|
||||
|
||||
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.
|
||||
* 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
|
||||
@ -107,8 +90,8 @@ public class CompactDecimalFormat extends DecimalFormat {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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
|
||||
@ -121,201 +104,130 @@ public class CompactDecimalFormat extends DecimalFormat {
|
||||
/**
|
||||
* The public mechanism is CompactDecimalFormat.getInstance().
|
||||
*
|
||||
* @param locale
|
||||
* the desired locale
|
||||
* @param style
|
||||
* the compact style
|
||||
* @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);
|
||||
// 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) {
|
||||
setGroupingUsed(false);
|
||||
// TODO: This was setGroupingUsed(false) in ICU 58. Is it okay that I changed it for ICU 59?
|
||||
properties.setMinimumGroupingDigits(2);
|
||||
}
|
||||
setCurrency(null);
|
||||
refreshFormatter();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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;
|
||||
return super.equals(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @stable ICU 49
|
||||
*/
|
||||
@Override
|
||||
public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) {
|
||||
return format(number, null, toAppendTo, 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();
|
||||
}
|
||||
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());
|
||||
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) {
|
||||
return format((double) number, toAppendTo, 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) {
|
||||
return format(number.doubleValue(), toAppendTo, 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) {
|
||||
return format(number.doubleValue(), toAppendTo, 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) {
|
||||
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);
|
||||
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
|
||||
@ -332,245 +244,4 @@ public class CompactDecimalFormat extends DecimalFormat {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -162,6 +162,10 @@ public class CurrencyPluralInfo implements Cloneable, Serializable {
|
||||
* 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
|
||||
@ -188,6 +192,7 @@ public class CurrencyPluralInfo implements Cloneable, Serializable {
|
||||
*
|
||||
* @stable ICU 4.2
|
||||
*/
|
||||
@Override
|
||||
public Object clone() {
|
||||
try {
|
||||
CurrencyPluralInfo other = (CurrencyPluralInfo) super.clone();
|
||||
@ -213,6 +218,7 @@ public class CurrencyPluralInfo implements Cloneable, Serializable {
|
||||
*
|
||||
* @stable ICU 4.2
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object a) {
|
||||
if (a instanceof CurrencyPluralInfo) {
|
||||
CurrencyPluralInfo other = (CurrencyPluralInfo)a;
|
||||
@ -223,15 +229,17 @@ public class CurrencyPluralInfo implements Cloneable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock implementation of hashCode(). This implementation always returns a constant
|
||||
* value. When Java assertion is enabled, this method triggers an assertion failure.
|
||||
* Override hashCode
|
||||
*
|
||||
* @internal
|
||||
* @deprecated This API is ICU internal only.
|
||||
*/
|
||||
@Override
|
||||
@Deprecated
|
||||
public int hashCode() {
|
||||
assert false : "hashCode not designed";
|
||||
return 42;
|
||||
return pluralCountToCurrencyUnitPattern.hashCode()
|
||||
^ pluralRules.hashCode()
|
||||
^ ulocale.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -232,8 +232,11 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
|
||||
* 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.
|
||||
*/
|
||||
String[] getDigitStringsLocal() {
|
||||
@Deprecated
|
||||
public String[] getDigitStringsLocal() {
|
||||
return digitStrings;
|
||||
}
|
||||
|
||||
@ -1318,9 +1321,9 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
|
||||
setMonetaryGroupingSeparatorString(numberElements[11]);
|
||||
setExponentMultiplicationSign(numberElements[12]);
|
||||
|
||||
digit = DecimalFormat.PATTERN_DIGIT; // Localized pattern character no longer in CLDR
|
||||
padEscape = DecimalFormat.PATTERN_PAD_ESCAPE;
|
||||
sigDigit = DecimalFormat.PATTERN_SIGNIFICANT_DIGIT;
|
||||
digit = '#'; // Localized pattern character no longer in CLDR
|
||||
padEscape = '*';
|
||||
sigDigit = '@';
|
||||
|
||||
|
||||
CurrencyDisplayInfo info = CurrencyData.provider.getInstance(locale, true);
|
||||
@ -1448,8 +1451,8 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
|
||||
exponential = 'E';
|
||||
}
|
||||
if (serialVersionOnStream < 2) {
|
||||
padEscape = DecimalFormat.PATTERN_PAD_ESCAPE;
|
||||
plusSign = DecimalFormat.PATTERN_PLUS_SIGN;
|
||||
padEscape = '*';
|
||||
plusSign = '+';
|
||||
exponentSeparator = String.valueOf(exponential);
|
||||
// Although we read the exponential field on stream to create the
|
||||
// exponentSeparator, we don't do the reverse, since scientific
|
||||
@ -1527,7 +1530,7 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
|
||||
groupingSeparatorString = String.valueOf(groupingSeparator);
|
||||
}
|
||||
if (percentString == null) {
|
||||
percentString = String.valueOf(percentString);
|
||||
percentString = String.valueOf(percent);
|
||||
}
|
||||
if (perMillString == null) {
|
||||
perMillString = String.valueOf(perMill);
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1020,7 +1020,12 @@ public class MeasureFormat extends UFormat {
|
||||
return pattern;
|
||||
}
|
||||
|
||||
private String getPluralFormatter(MeasureUnit unit, FormatWidth width, int index) {
|
||||
/**
|
||||
* @internal
|
||||
* @deprecated This API is ICU internal only.
|
||||
*/
|
||||
@Deprecated
|
||||
public String getPluralFormatter(MeasureUnit unit, FormatWidth width, int index) {
|
||||
if (index != StandardPlural.OTHER_INDEX) {
|
||||
String pattern = getFormatterOrNull(unit, width, index);
|
||||
if (pattern != null) {
|
||||
@ -1171,6 +1176,7 @@ public class MeasureFormat extends UFormat {
|
||||
suffix = pattern.substring(pos+3);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return prefix + "; " + suffix;
|
||||
}
|
||||
@ -1404,6 +1410,7 @@ public class MeasureFormat extends UFormat {
|
||||
public MeasureProxy() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeExternal(ObjectOutput out) throws IOException {
|
||||
out.writeByte(0); // version
|
||||
out.writeUTF(locale.toLanguageTag());
|
||||
@ -1413,6 +1420,7 @@ public class MeasureFormat extends UFormat {
|
||||
out.writeObject(keyValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
|
||||
in.readByte(); // version.
|
||||
|
@ -38,7 +38,7 @@ import com.ibm.icu.impl.PatternProps;
|
||||
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.FixedDecimal;
|
||||
import com.ibm.icu.text.PluralRules.IFixedDecimal;
|
||||
import com.ibm.icu.text.PluralRules.PluralType;
|
||||
import com.ibm.icu.util.ICUUncheckedIOException;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
@ -2108,7 +2108,7 @@ public class MessageFormat extends UFormat {
|
||||
assert context.number.doubleValue() == number; // argument number minus the offset
|
||||
context.numberString = context.formatter.format(context.number);
|
||||
if(context.formatter instanceof DecimalFormat) {
|
||||
FixedDecimal dec = ((DecimalFormat)context.formatter).getFixedDecimal(number);
|
||||
IFixedDecimal dec = ((DecimalFormat)context.formatter).getFixedDecimal(number);
|
||||
return rules.select(dec);
|
||||
} else {
|
||||
return rules.select(number);
|
||||
|
@ -497,8 +497,13 @@ public abstract class NumberFormat extends UFormat {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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>
|
||||
* {@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>
|
||||
* <li>Leading or doubled grouping separators<br>
|
||||
* ',123' and '1,,234" fail</li>
|
||||
* <li>Groups of incorrect length when grouping is used<br>
|
||||
@ -1113,8 +1118,13 @@ public abstract class NumberFormat extends UFormat {
|
||||
/**
|
||||
* 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, the exact behavior when this value is exceeded is
|
||||
* subclass-specific. When parsing, this has no effect.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @return the maximum number of integer digits
|
||||
* @see #setMaximumIntegerDigits
|
||||
* @stable ICU 2.0
|
||||
@ -1415,10 +1425,12 @@ public abstract class NumberFormat extends UFormat {
|
||||
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
|
||||
@ -1449,8 +1461,11 @@ public abstract class NumberFormat extends UFormat {
|
||||
* @param choice the pattern format.
|
||||
* @return the pattern
|
||||
* @stable ICU 3.2
|
||||
* @internal
|
||||
* @deprecated This API is ICU internal only.
|
||||
*/
|
||||
protected static String getPattern(ULocale forLocale, int choice) {
|
||||
@Deprecated
|
||||
public 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
|
||||
@ -1460,6 +1475,7 @@ public abstract class NumberFormat extends UFormat {
|
||||
switch (choice) {
|
||||
case NUMBERSTYLE:
|
||||
case INTEGERSTYLE:
|
||||
case PLURALCURRENCYSTYLE:
|
||||
patternKey = "decimalFormat";
|
||||
break;
|
||||
case CURRENCYSTYLE:
|
||||
@ -1469,7 +1485,6 @@ public abstract class NumberFormat extends UFormat {
|
||||
break;
|
||||
case CASHCURRENCYSTYLE:
|
||||
case ISOCURRENCYSTYLE:
|
||||
case PLURALCURRENCYSTYLE:
|
||||
case STANDARDCURRENCYSTYLE:
|
||||
patternKey = "currencyFormat";
|
||||
break;
|
||||
|
@ -18,6 +18,8 @@ import java.util.Map;
|
||||
|
||||
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;
|
||||
@ -554,8 +556,8 @@ public class PluralFormat extends UFormat {
|
||||
private final class PluralSelectorAdapter implements PluralSelector {
|
||||
@Override
|
||||
public String select(Object context, double number) {
|
||||
FixedDecimal dec = (FixedDecimal) context;
|
||||
assert dec.source == (dec.isNegative ? -number : number);
|
||||
IFixedDecimal dec = (IFixedDecimal) context;
|
||||
assert dec.getPluralOperand(Operand.n) == Math.abs(number);
|
||||
return pluralRules.select(dec);
|
||||
}
|
||||
}
|
||||
@ -618,7 +620,7 @@ public class PluralFormat extends UFormat {
|
||||
} else {
|
||||
numberString = numberFormat.format(numberMinusOffset);
|
||||
}
|
||||
FixedDecimal dec;
|
||||
IFixedDecimal dec;
|
||||
if(numberFormat instanceof DecimalFormat) {
|
||||
dec = ((DecimalFormat) numberFormat).getFixedDecimal(numberMinusOffset);
|
||||
} else {
|
||||
|
@ -356,7 +356,7 @@ public class PluralRules implements Serializable {
|
||||
private static final long serialVersionUID = 9163464945387899416L;
|
||||
|
||||
@Override
|
||||
public boolean isFulfilled(FixedDecimal n) {
|
||||
public boolean isFulfilled(IFixedDecimal n) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -412,11 +412,21 @@ public class PluralRules implements Serializable {
|
||||
*/
|
||||
public static final PluralRules DEFAULT = new PluralRules(new RuleList().addRule(DEFAULT_RULE));
|
||||
|
||||
private enum Operand {
|
||||
/**
|
||||
* @internal
|
||||
* @deprecated This API is ICU internal only.
|
||||
*/
|
||||
@Deprecated
|
||||
public static enum Operand {
|
||||
/** The double value of the entire number. */
|
||||
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 */
|
||||
@ -428,7 +438,18 @@ public class PluralRules implements Serializable {
|
||||
* @deprecated This API is ICU internal only.
|
||||
*/
|
||||
@Deprecated
|
||||
public static class FixedDecimal extends Number implements Comparable<FixedDecimal> {
|
||||
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 {
|
||||
private static final long serialVersionUID = -4756200506571685661L;
|
||||
/**
|
||||
* @internal
|
||||
@ -726,8 +747,9 @@ public class PluralRules implements Serializable {
|
||||
* @internal
|
||||
* @deprecated This API is ICU internal only.
|
||||
*/
|
||||
@Override
|
||||
@Deprecated
|
||||
public double get(Operand operand) {
|
||||
public double getPluralOperand(Operand operand) {
|
||||
switch(operand) {
|
||||
default: return source;
|
||||
case i: return integerValue;
|
||||
@ -881,6 +903,22 @@ public class PluralRules implements Serializable {
|
||||
) 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1106,7 +1144,7 @@ public class PluralRules implements Serializable {
|
||||
* Returns true if the number fulfills the constraint.
|
||||
* @param n the number to test, >= 0.
|
||||
*/
|
||||
boolean isFulfilled(FixedDecimal n);
|
||||
boolean isFulfilled(IFixedDecimal n);
|
||||
|
||||
/*
|
||||
* Returns false if an unlimited number of values fulfills the
|
||||
@ -1463,10 +1501,10 @@ public class PluralRules implements Serializable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFulfilled(FixedDecimal number) {
|
||||
double n = number.get(operand);
|
||||
public boolean isFulfilled(IFixedDecimal number) {
|
||||
double n = number.getPluralOperand(operand);
|
||||
if ((integersOnly && (n - (long)n) != 0.0
|
||||
|| operand == Operand.j && number.visibleDecimalDigitCount != 0)) {
|
||||
|| operand == Operand.j && number.getPluralOperand(Operand.v) != 0)) {
|
||||
return !inRange;
|
||||
}
|
||||
if (mod != 0) {
|
||||
@ -1566,7 +1604,7 @@ public class PluralRules implements Serializable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFulfilled(FixedDecimal n) {
|
||||
public boolean isFulfilled(IFixedDecimal n) {
|
||||
return a.isFulfilled(n)
|
||||
&& b.isFulfilled(n);
|
||||
}
|
||||
@ -1594,7 +1632,7 @@ public class PluralRules implements Serializable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFulfilled(FixedDecimal n) {
|
||||
public boolean isFulfilled(IFixedDecimal n) {
|
||||
return a.isFulfilled(n)
|
||||
|| b.isFulfilled(n);
|
||||
}
|
||||
@ -1645,7 +1683,7 @@ public class PluralRules implements Serializable {
|
||||
return keyword;
|
||||
}
|
||||
|
||||
public boolean appliesTo(FixedDecimal n) {
|
||||
public boolean appliesTo(IFixedDecimal n) {
|
||||
return constraint.isFulfilled(n);
|
||||
}
|
||||
|
||||
@ -1708,7 +1746,7 @@ public class PluralRules implements Serializable {
|
||||
return this;
|
||||
}
|
||||
|
||||
private Rule selectRule(FixedDecimal n) {
|
||||
private Rule selectRule(IFixedDecimal n) {
|
||||
for (Rule rule : rules) {
|
||||
if (rule.appliesTo(n)) {
|
||||
return rule;
|
||||
@ -1717,8 +1755,8 @@ public class PluralRules implements Serializable {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String select(FixedDecimal n) {
|
||||
if (Double.isInfinite(n.source) || Double.isNaN(n.source)) {
|
||||
public String select(IFixedDecimal n) {
|
||||
if (n.isInfinite() || n.isNaN()) {
|
||||
return KEYWORD_OTHER;
|
||||
}
|
||||
Rule r = selectRule(n);
|
||||
@ -1780,7 +1818,7 @@ public class PluralRules implements Serializable {
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean select(FixedDecimal sample, String keyword) {
|
||||
public boolean select(IFixedDecimal sample, String keyword) {
|
||||
for (Rule rule : rules) {
|
||||
if (rule.getKeyword().equals(keyword) && rule.appliesTo(sample)) {
|
||||
return true;
|
||||
@ -1800,9 +1838,9 @@ public class PluralRules implements Serializable {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private boolean addConditional(Set<FixedDecimal> toAddTo, Set<FixedDecimal> others, double trial) {
|
||||
private boolean addConditional(Set<IFixedDecimal> toAddTo, Set<IFixedDecimal> others, double trial) {
|
||||
boolean added;
|
||||
FixedDecimal toAdd = new FixedDecimal(trial);
|
||||
IFixedDecimal toAdd = new FixedDecimal(trial);
|
||||
if (!toAddTo.contains(toAdd) && !others.contains(toAdd)) {
|
||||
others.add(toAdd);
|
||||
added = true;
|
||||
@ -1969,7 +2007,7 @@ public class PluralRules implements Serializable {
|
||||
* @deprecated This API is ICU internal only.
|
||||
*/
|
||||
@Deprecated
|
||||
public String select(FixedDecimal number) {
|
||||
public String select(IFixedDecimal number) {
|
||||
return rules.select(number);
|
||||
}
|
||||
|
||||
|
@ -1267,7 +1267,7 @@ public class RuleBasedNumberFormat extends NumberFormat {
|
||||
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);
|
||||
|
@ -13,6 +13,7 @@ import java.text.AttributedCharacterIterator.Attribute;
|
||||
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;
|
||||
|
||||
@ -229,14 +230,14 @@ public final class ScientificNumberFormatter {
|
||||
int start = iterator.getRunStart(NumberFormat.Field.EXPONENT_SIGN);
|
||||
int limit = iterator.getRunLimit(NumberFormat.Field.EXPONENT_SIGN);
|
||||
int aChar = char32AtAndAdvance(iterator);
|
||||
if (DecimalFormat.minusSigns.contains(aChar)) {
|
||||
if (Parse.UNISET_MINUS.contains(aChar)) {
|
||||
append(
|
||||
iterator,
|
||||
copyFromOffset,
|
||||
start,
|
||||
result);
|
||||
result.append(SUPERSCRIPT_MINUS_SIGN);
|
||||
} else if (DecimalFormat.plusSigns.contains(aChar)) {
|
||||
} else if (Parse.UNISET_PLUS.contains(aChar)) {
|
||||
append(
|
||||
iterator,
|
||||
copyFromOffset,
|
||||
|
@ -674,19 +674,7 @@ public class Currency extends MeasureUnit {
|
||||
*/
|
||||
@Deprecated
|
||||
public static String parse(ULocale locale, String text, int type, ParsePosition pos) {
|
||||
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);
|
||||
}
|
||||
|
||||
List<TextTrieMap<CurrencyStringInfo>> currencyTrieVec = getCurrencyTrieVec(locale);
|
||||
int maxLength = 0;
|
||||
String isoResult = null;
|
||||
|
||||
@ -711,6 +699,37 @@ public class Currency extends MeasureUnit {
|
||||
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) {
|
||||
|
||||
@ -734,7 +753,12 @@ public class Currency extends MeasureUnit {
|
||||
}
|
||||
}
|
||||
|
||||
private static final class CurrencyStringInfo {
|
||||
/**
|
||||
* @internal
|
||||
* @deprecated This API is ICU internal only.
|
||||
*/
|
||||
@Deprecated
|
||||
public static final class CurrencyStringInfo {
|
||||
private String isoCode;
|
||||
private String currencyString;
|
||||
|
||||
|
@ -43,7 +43,7 @@ public class Measure {
|
||||
*/
|
||||
public Measure(Number number, MeasureUnit unit) {
|
||||
if (number == null || unit == null) {
|
||||
throw new NullPointerException();
|
||||
throw new NullPointerException("Number and MeasureUnit must not be null");
|
||||
}
|
||||
this.number = number;
|
||||
this.unit = unit;
|
||||
|
@ -2,17 +2,17 @@
|
||||
<launchConfiguration type="org.eclipse.ant.AntLaunchConfigurationType">
|
||||
<booleanAttribute key="org.eclipse.ant.ui.DEFAULT_VM_INSTALL" value="false"/>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
|
||||
<listEntry value="/icu4j-collate-tests/build.xml"/>
|
||||
<listEntry value="/icu4j-core-tests"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
|
||||
<listEntry value="1"/>
|
||||
<listEntry value="4"/>
|
||||
</listAttribute>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="org.eclipse.ant.ui.AntClasspathProvider"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="icu4j-core-tests"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.ant.ui.AntClasspathProvider"/>
|
||||
<mapAttribute key="org.eclipse.ui.externaltools.ATTR_ANT_PROPERTIES">
|
||||
<mapEntry key="eclipse.running" value="true"/>
|
||||
<mapEntry key="build.compiler" value="org.eclipse.jdt.core.JDTCompilerAdapter"/>
|
||||
<mapEntry key="eclipse.running" value="true"/>
|
||||
</mapAttribute>
|
||||
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_ANT_PROPERTY_FILES" value="${workspace_loc:/icu4j-shared/build/locations-eclipse.properties},"/>
|
||||
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/icu4j-collate-tests/build.xml}"/>
|
||||
|
@ -5,18 +5,18 @@
|
||||
<booleanAttribute key="org.eclipse.ant.ui.ATTR_TARGETS_UPDATED" value="true"/>
|
||||
<booleanAttribute key="org.eclipse.ant.ui.DEFAULT_VM_INSTALL" value="false"/>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
|
||||
<listEntry value="/icu4j-core-tests/build.xml"/>
|
||||
<listEntry value="/icu4j-core-tests"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
|
||||
<listEntry value="1"/>
|
||||
<listEntry value="4"/>
|
||||
</listAttribute>
|
||||
<booleanAttribute key="org.eclipse.debug.ui.ATTR_LAUNCH_IN_BACKGROUND" value="false"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="org.eclipse.ant.ui.AntClasspathProvider"/>
|
||||
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="true"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="icu4j-core-tests"/>
|
||||
<mapAttribute key="org.eclipse.ui.externaltools.ATTR_ANT_PROPERTIES">
|
||||
<mapEntry key="eclipse.running" value="true"/>
|
||||
<mapEntry key="build.compiler" value="org.eclipse.jdt.core.JDTCompilerAdapter"/>
|
||||
<mapEntry key="eclipse.running" value="true"/>
|
||||
</mapAttribute>
|
||||
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_ANT_PROPERTY_FILES" value="${workspace_loc:/icu4j-shared/build/locations-eclipse.properties},"/>
|
||||
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/icu4j-core-tests/build.xml}"/>
|
||||
|
@ -2,17 +2,17 @@
|
||||
<launchConfiguration type="org.eclipse.ant.AntLaunchConfigurationType">
|
||||
<booleanAttribute key="org.eclipse.ant.ui.DEFAULT_VM_INSTALL" value="false"/>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
|
||||
<listEntry value="/icu4j-core-tests/build.xml"/>
|
||||
<listEntry value="/icu4j-core-tests"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
|
||||
<listEntry value="1"/>
|
||||
<listEntry value="4"/>
|
||||
</listAttribute>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="org.eclipse.ant.ui.AntClasspathProvider"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="icu4j-core-tests"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.ant.ui.AntClasspathProvider"/>
|
||||
<mapAttribute key="org.eclipse.ui.externaltools.ATTR_ANT_PROPERTIES">
|
||||
<mapEntry key="eclipse.running" value="true"/>
|
||||
<mapEntry key="build.compiler" value="org.eclipse.jdt.core.JDTCompilerAdapter"/>
|
||||
<mapEntry key="eclipse.running" value="true"/>
|
||||
</mapAttribute>
|
||||
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_ANT_PROPERTY_FILES" value="${workspace_loc:/icu4j-shared/build/locations-eclipse.properties},"/>
|
||||
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/icu4j-core-tests/build.xml}"/>
|
||||
|
@ -17,8 +17,8 @@ set locale ar
|
||||
set pattern +0;-#
|
||||
begin
|
||||
format output breaks
|
||||
6 \u200F+\u0666 JK
|
||||
-6 \u200F-\u0666 JK
|
||||
6 \u061C+\u0666 JK
|
||||
-6 \u061C-\u0666 K
|
||||
|
||||
test basic patterns
|
||||
set locale fr_FR
|
||||
@ -54,6 +54,75 @@ format output
|
||||
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
|
||||
@ -113,12 +182,36 @@ pattern format output breaks
|
||||
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
|
||||
@ -165,7 +258,7 @@ $**####,##0 1234 $***1\u00a0234 K
|
||||
// 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,00 JK
|
||||
\u00a4\u00a4 **#######0 433.0 EUR ****433 JK
|
||||
|
||||
test padding and currencies
|
||||
begin
|
||||
@ -235,13 +328,16 @@ set pattern #E0
|
||||
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
|
||||
0 0 0 0 2.99792458E8 K
|
||||
// JDK gives .299792E9
|
||||
0 1 0 5 2.9979E8 K
|
||||
// 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
|
||||
// JDK gives 300E6
|
||||
0 3 0 0 299.792458E6 K
|
||||
// JDK gives 299.8E6 (maybe maxInt + maxFrac instead of minInt + maxFrac)?
|
||||
@ -256,11 +352,14 @@ minIntegerDigits maxIntegerDigits minFractionDigits maxFractionDigits output bre
|
||||
4 4 0 0 2998E5
|
||||
0 0 1 5 .29979E9
|
||||
// JDK gives E0
|
||||
0 0 1 0 2.99792458E8 K
|
||||
// S obeys the maximum integer digits
|
||||
0 0 1 0 2.99792458E8 KS
|
||||
// JDK gives .2998E9
|
||||
0 0 0 4 2.998E8 K
|
||||
0 0 0 4 2.998E8 KS
|
||||
// S correctly formats this as 29.979246E7.
|
||||
// JDK uses 8 + 6 for significant digits instead of 2 + 6
|
||||
2 8 1 6 2.9979246E8 K
|
||||
// J and C return 2.9979246E8.
|
||||
2 8 1 6 29.979246E7 CJK
|
||||
// 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.
|
||||
@ -290,7 +389,8 @@ set format 29979245.0
|
||||
begin
|
||||
minIntegerDigits maxIntegerDigits minFractionDigits maxFractionDigits output breaks
|
||||
// JDK gives E0
|
||||
0 0 0 0 2.9979245E7 K
|
||||
// S obeys the max integer digits and prints 0.299...
|
||||
0 0 0 0 2.9979245E7 KS
|
||||
// JDK gives .3E8
|
||||
0 1 0 0 2.9979245E7 K
|
||||
// JDK gives 2998E4.
|
||||
@ -300,23 +400,27 @@ test ticket 11524
|
||||
set locale en
|
||||
set pattern #,##0.###
|
||||
begin
|
||||
format maxIntegerDigits output
|
||||
format maxIntegerDigits output breaks
|
||||
123 1 3
|
||||
123 -2147483648 0
|
||||
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
|
||||
12345 -2147483648 0 S
|
||||
5.3 1 5.3
|
||||
5.3 -2147483648 .3
|
||||
5.3 -2147483648 .3 S
|
||||
|
||||
test patterns with zero
|
||||
set locale en
|
||||
set format 0
|
||||
begin
|
||||
pattern output
|
||||
pattern output breaks
|
||||
#.# 0
|
||||
#. 0.
|
||||
.# .0
|
||||
# 0
|
||||
#,##0.00 0.00
|
||||
#,###.00 .00
|
||||
00.000E00 00.000E00
|
||||
0.####E0 0E0
|
||||
##0.######E000 0E000
|
||||
@ -334,8 +438,8 @@ format output breaks
|
||||
0.001234 0.001234 K
|
||||
0.0012345 0.0012345 K
|
||||
0.00123456 0.0012346 K
|
||||
-43 -43.0 K
|
||||
-43.7 -43.7 K
|
||||
-43 -43.0
|
||||
-43.7 -43.7
|
||||
-43.76 -43.76 K
|
||||
-43.762 -43.762 K
|
||||
-43.7626 -43.763 K
|
||||
@ -360,7 +464,7 @@ output grouping breaks grouping2 minGroupingDigits
|
||||
1,2345,6789 4
|
||||
1,23,45,6789 4 K 2
|
||||
1,23,45,6789 4 K 2 2
|
||||
123,456789 6 K 6 3
|
||||
123,456789 6 6 3
|
||||
123456789 6 JK 6 4
|
||||
|
||||
test multiplier setters
|
||||
@ -370,7 +474,7 @@ format multiplier output breaks
|
||||
23 -12 -276
|
||||
23 -1 -23
|
||||
// ICU4J and JDK throw exception on zero multiplier. ICU4C does not.
|
||||
23 0 23 JK
|
||||
23 0 23 JKS
|
||||
23 1 23
|
||||
23 12 276
|
||||
-23 12 -276
|
||||
@ -394,7 +498,7 @@ set pattern bill0
|
||||
set format 1357
|
||||
begin
|
||||
padCharacter formatWidth output breaks
|
||||
* 8 bill1357 K
|
||||
* 8 bill1357
|
||||
* 9 *bill1357 K
|
||||
^ 10 ^^bill1357 K
|
||||
|
||||
@ -406,7 +510,7 @@ begin
|
||||
output breaks useScientific
|
||||
186283.00
|
||||
1.86E5 K 1
|
||||
186283.00 K 0
|
||||
186283.00 0
|
||||
|
||||
test rounding mode setters
|
||||
set locale en_US
|
||||
@ -423,19 +527,43 @@ format roundingMode output breaks
|
||||
-1.49 down -1 K
|
||||
1.01 up 1.5 K
|
||||
1.49 down 1 K
|
||||
-1.01 ceiling -1 K
|
||||
-1.49 floor -1.5 K
|
||||
-1.01 ceiling -1
|
||||
-1.49 floor -1.5
|
||||
|
||||
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 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
|
||||
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
|
||||
|
||||
test exponent parameter setters
|
||||
set locale en_US
|
||||
@ -445,12 +573,10 @@ begin
|
||||
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 JK
|
||||
1 1 3 3.E+008 JK
|
||||
1 0 2 3.E08 K
|
||||
1 1 3 3.E+008 K
|
||||
1 0 1 3.E8
|
||||
0 0 1 3E8
|
||||
|
||||
@ -462,7 +588,7 @@ format output breaks decimalSeparatorAlwaysShown
|
||||
// decimalSeparatorAlwaysShown off by default
|
||||
299792458 3E8
|
||||
299000000 2.99E8
|
||||
299792458 3.E8 J 1
|
||||
299792458 3.E8 1
|
||||
|
||||
test pad position setters
|
||||
set locale en_US
|
||||
@ -505,7 +631,7 @@ set locale en_US
|
||||
set pattern [0.00];(#)
|
||||
begin
|
||||
format output breaks
|
||||
Inf [\u221e] K
|
||||
Inf [\u221e]
|
||||
-Inf (\u221e) K
|
||||
NaN NaN K
|
||||
|
||||
@ -539,36 +665,38 @@ begin
|
||||
locale pattern format output breaks
|
||||
en #0% 0.4376 44%
|
||||
// This next test breaks JDK. JDK doesn't multiply by 100.
|
||||
// It also is now broken in ICU4J until #10368 is fixed.
|
||||
fa \u0025\u00a0\u0023\u0030 0.4376 \u200e\u066a\u00a0\u06f4\u06f4 JK
|
||||
fa \u0025\u00a0\u0023\u0030 0.4376 \u200e\u066a\u00a0\u06f4\u06f4 K
|
||||
|
||||
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 K
|
||||
**####0,000 **#,##0,000 KS
|
||||
###,000. #,000.
|
||||
0,000 #0,000
|
||||
0,000 #0,000 S
|
||||
.00 #.00
|
||||
000 #000
|
||||
000,000 #,000,000
|
||||
000 #000 S
|
||||
000,000 #,000,000 S
|
||||
pp#,000 pp#,000
|
||||
00.## #00.##
|
||||
00.## #00.## S
|
||||
#,#00.025 #,#00.025
|
||||
// No secondary grouping in JDK
|
||||
#,##,###.02500 #,##,###.02500 K
|
||||
pp#,000;(#) pp#,000;(#,000) K
|
||||
**####,##,##0.0##;(#) **#,##,##,##0.0##;**(##,##,##0.0##) K
|
||||
**####,##,##0.0##;(#) **#,##,##,##0.0##;**(##,##,##0.0##) KS
|
||||
// No significant digits in JDK
|
||||
@@### @@### K
|
||||
@,@#,### @,@#,### K
|
||||
0.00E0 0.00E0
|
||||
@@@##E0 @@@##E0 K
|
||||
// The following one works in JDK, probably because
|
||||
// it just returns the same string
|
||||
@@@##E0 @@@##E0
|
||||
###0.00#E0 ###0.00#E0
|
||||
##00.00#E0 ##00.00#E0
|
||||
0.00E+00 0.00E+00 K
|
||||
@ -589,7 +717,8 @@ parse output breaks
|
||||
// J requires prefix and suffix for lenient parsing, but C doesn't
|
||||
5,347.25 5347.25 JK
|
||||
(5,347.25 -5347.25 J
|
||||
-5,347.25 fail
|
||||
// S is successful at parsing this as -5347.25 in lenient mode
|
||||
-5,347.25 fail S
|
||||
+3.52E4 35200
|
||||
(34.8E-3) -0.0348
|
||||
// JDK stops parsing at the spaces. JDK doesn't see space as a grouping separator
|
||||
@ -598,7 +727,7 @@ parse output breaks
|
||||
// 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) -3425 J
|
||||
(34,,25 E-1) -342.5 JK
|
||||
// Spaces are not allowed after exponent symbol
|
||||
// C parses up to the E but J bails
|
||||
(34 25E -1) -3425 JK
|
||||
@ -648,16 +777,16 @@ set locale en
|
||||
set pattern #,##0.0###+;#-
|
||||
begin
|
||||
parse output breaks
|
||||
// C sees this as -3426, don't understand why
|
||||
3426 -3426 JK
|
||||
// C sees this as -3426, don't understand why.
|
||||
// J and K just bail.
|
||||
3426 3426 JKC
|
||||
3426+ 3426
|
||||
// J bails, but JDK will parse up to the space and get 34.
|
||||
// C sees -34
|
||||
34 d1+ -34 JK
|
||||
// J bails; C and K see -34
|
||||
34 d1+ 34 JKC
|
||||
// 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 JK
|
||||
1,234,,,+ 1234 JKC
|
||||
1,234- -1234
|
||||
// J bails because of trailing separators
|
||||
1,234,- -1234 J
|
||||
@ -668,55 +797,70 @@ parse output breaks
|
||||
|
||||
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.
|
||||
+5,347.25 5347.25
|
||||
(5347.25) -5347.25
|
||||
(5,347.25) -5347.25
|
||||
+65,347.25 65347.25
|
||||
(65347.25) -65347.25
|
||||
(65,347.25) -65347.25
|
||||
// JDK does allow separators in the wrong place and parses as -5347.25
|
||||
(53,47.25) fail K
|
||||
// strict requires prefix or suffix
|
||||
5,347.25 fail
|
||||
65,347.25 fail
|
||||
+3.52E4 35200
|
||||
(34.8E-3) -0.0348
|
||||
(3425E-1) -342.5
|
||||
// Strict doesn't allow separators in sci notation.
|
||||
(3,425) -3425
|
||||
// JDK allows separators in sci notation and parses as -342.5
|
||||
(3,425E-1) fail K
|
||||
(63,425) -63425
|
||||
// JDK and S allow separators in sci notation and parses as -342.5
|
||||
(63,425E-1) fail KS
|
||||
// Both prefix and suffix needed for strict.
|
||||
// JDK accepts this and parses as -342.5
|
||||
(3425E-1 fail K
|
||||
+3.52EE4 3.52
|
||||
+1,234,567.8901 1234567.8901
|
||||
+12,34,567.8901 1234567.8901
|
||||
// With strict digit separators don't have to be the right type
|
||||
// JDK doesn't acknowledge space as a separator
|
||||
+1 234 567.8901 1234567.8901 K
|
||||
+12 34 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.
|
||||
+1,234,567.8901 1234567.8901
|
||||
// JDK doesn't require separators to be in the right place
|
||||
+12,345.67 12345.67
|
||||
// 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
|
||||
+123,456.78,9 123456.78
|
||||
// A decimal after a decimal means bail
|
||||
// JDK parses as 123456.78
|
||||
+123,456.78.9 fail K
|
||||
+1,23,456.78,9 123456.78
|
||||
// J fails upon seeing the second decimal point
|
||||
+1,23,456.78.9 123456.78 J
|
||||
+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
|
||||
@ -724,7 +868,8 @@ set parseIntegerOnly 1
|
||||
begin
|
||||
parse output breaks
|
||||
35 35
|
||||
+35 fail
|
||||
// S accepts leading plus signs
|
||||
+35 35 CJK
|
||||
-35 -35
|
||||
2.63 2
|
||||
-39.99 -39
|
||||
@ -746,8 +891,8 @@ set pattern 0
|
||||
set locale en
|
||||
begin
|
||||
parse output outputCurrency breaks
|
||||
// See ticket 11735
|
||||
53.45 fail USD J
|
||||
// Fixed in ticket 11735
|
||||
53.45 fail USD
|
||||
|
||||
test parse strange prefix
|
||||
set locale en
|
||||
@ -775,12 +920,13 @@ set negativePrefix
|
||||
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
|
||||
@ -791,6 +937,39 @@ parse output
|
||||
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
|
||||
@ -811,25 +990,28 @@ NaN 0.0 other
|
||||
|
||||
test parse currency ISO
|
||||
set pattern 0.00 \u00a4\u00a4;(#) \u00a4\u00a4
|
||||
set locale en_US
|
||||
set locale en_GB
|
||||
begin
|
||||
parse output outputCurrency breaks
|
||||
$53.45 53.45 USD
|
||||
53.45 fail GBP
|
||||
£53.45 53.45 GBP
|
||||
$53.45 fail USD
|
||||
53.45 USD 53.45 USD
|
||||
53.45 GBP 53.45 GBP
|
||||
USD 53.45 53.45 USD J
|
||||
53.45USD fail USD
|
||||
53.45USD 53.45 USD CJ
|
||||
USD53.45 53.45 USD
|
||||
(7.92) USD -7.92 USD
|
||||
(7.92) EUR -7.92 EUR
|
||||
(7.92) GBP -7.92 GBP
|
||||
(7.926) USD -7.926 USD
|
||||
(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
|
||||
(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
|
||||
(8) USD -8 USD
|
||||
-8 USD fail USD
|
||||
-8 USD -8 USD CJ
|
||||
67 USD 67 USD
|
||||
53.45$ fail USD
|
||||
US Dollars 53.45 53.45 USD J
|
||||
@ -837,37 +1019,41 @@ 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 fail USD
|
||||
53.45US Dollars 53.45 USD CJ
|
||||
US Dollar53.45 53.45 USD
|
||||
US Dollat53.45 fail USD
|
||||
53.45US Dollar fail USD
|
||||
US Dollars (53.45) fail USD
|
||||
53.45US Dollar 53.45 USD CJ
|
||||
US Dollars (53.45) -53.45 USD CJ
|
||||
(53.45) US Dollars -53.45 USD
|
||||
US Dollar (53.45) fail USD
|
||||
(53.45) Euros -53.45 EUR
|
||||
US Dollar (53.45) -53.45 USD CJ
|
||||
(53.45) US Dollar -53.45 USD
|
||||
US Dollars(53.45) fail USD
|
||||
(53.45)US Dollars fail USD
|
||||
US Dollar(53.45) fail 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 Dollat(53.45) fail USD
|
||||
(53.45)US Dollar fail USD
|
||||
(53.45)US Dollar -53.45 USD CJ
|
||||
|
||||
|
||||
test parse currency ISO negative
|
||||
set pattern 0.00 \u00a4\u00a4;-# \u00a4\u00a4
|
||||
set locale en_US
|
||||
set locale en_GB
|
||||
begin
|
||||
parse output outputCurrency breaks
|
||||
$53.45 53.45 USD
|
||||
53.45 fail GBP
|
||||
£53.45 53.45 GBP
|
||||
$53.45 fail USD
|
||||
53.45 USD 53.45 USD
|
||||
53.45 GBP 53.45 GBP
|
||||
USD 53.45 53.45 USD J
|
||||
53.45USD fail USD
|
||||
53.45USD 53.45 USD CJ
|
||||
USD53.45 53.45 USD
|
||||
-7.92 USD -7.92 USD
|
||||
-7.92 EUR -7.92 EUR
|
||||
-7.92 GBP -7.92 GBP
|
||||
-7.926 USD -7.926 USD
|
||||
USD -7.926 fail USD
|
||||
-7.92USD fail USD
|
||||
USD-7.92 fail USD
|
||||
USD -7.926 -7.926 USD CJ
|
||||
-7.92USD -7.92 USD CJ
|
||||
USD-7.92 -7.92 USD CJ
|
||||
-8 USD -8 USD
|
||||
67 USD 67 USD
|
||||
53.45$ fail USD
|
||||
@ -876,70 +1062,75 @@ 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 fail USD
|
||||
53.45US Dollars 53.45 USD CJ
|
||||
US Dollar53.45 53.45 USD
|
||||
US Dollat53.45 fail USD
|
||||
53.45US Dollar fail USD
|
||||
53.45US Dollar 53.45 USD CJ
|
||||
|
||||
|
||||
test parse currency long
|
||||
set pattern 0.00 \u00a4\u00a4\u00a4;(#) \u00a4\u00a4\u00a4
|
||||
set locale en_US
|
||||
set locale en_GB
|
||||
begin
|
||||
parse output outputCurrency breaks
|
||||
$53.45 53.45 USD
|
||||
// J throws a NullPointerException on the first case
|
||||
53.45 fail GBP
|
||||
£53.45 53.45 GBP
|
||||
$53.45 fail USD
|
||||
53.45 USD 53.45 USD
|
||||
53.45 GBP 53.45 GBP
|
||||
USD 53.45 53.45 USD J
|
||||
// See ticket 11735
|
||||
53.45USD fail USD J
|
||||
53.45USD 53.45 USD CJ
|
||||
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) 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
|
||||
(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
|
||||
(8) USD -8 USD
|
||||
// See ticket 11735
|
||||
-8 USD fail USD J
|
||||
-8 USD -8 USD CJ
|
||||
67 USD 67 USD
|
||||
// See ticket 11735
|
||||
53.45$ fail USD J
|
||||
// J throws a NullPointerException on the next case
|
||||
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
|
||||
// See ticket 11735
|
||||
53.45US Dollars fail USD J
|
||||
53.45US Dollars 53.45 USD CJ
|
||||
US Dollar53.45 53.45 USD
|
||||
US Dollat53.45 fail USD
|
||||
// See ticket 11735
|
||||
53.45US Dollar fail USD J
|
||||
53.45US Dollar 53.45 USD CJ
|
||||
|
||||
|
||||
test parse currency short
|
||||
set pattern 0.00 \u00a4;(#) \u00a4
|
||||
set locale en_US
|
||||
set locale en_GB
|
||||
begin
|
||||
parse output outputCurrency breaks
|
||||
$53.45 53.45 USD
|
||||
53.45 fail GBP
|
||||
£53.45 53.45 GBP
|
||||
$53.45 fail USD
|
||||
53.45 USD 53.45 USD
|
||||
53.45 GBP 53.45 GBP
|
||||
USD 53.45 53.45 USD J
|
||||
53.45USD fail USD
|
||||
53.45USD 53.45 USD CJ
|
||||
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) 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
|
||||
(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
|
||||
(8) USD -8 USD
|
||||
-8 USD fail USD
|
||||
-8 USD -8 USD CJ
|
||||
67 USD 67 USD
|
||||
53.45$ fail USD
|
||||
US Dollars 53.45 53.45 USD J
|
||||
@ -947,45 +1138,51 @@ 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 fail USD
|
||||
53.45US Dollars 53.45 USD CJ
|
||||
US Dollar53.45 53.45 USD
|
||||
US Dollat53.45 fail USD
|
||||
53.45US Dollar fail USD
|
||||
53.45US Dollar 53.45 USD CJ
|
||||
|
||||
|
||||
test parse currency short prefix
|
||||
set pattern \u00a40.00;(\u00a4#)
|
||||
set locale en_US
|
||||
set locale en_GB
|
||||
begin
|
||||
parse output outputCurrency breaks
|
||||
$53.45 53.45 USD
|
||||
53.45 USD fail USD
|
||||
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
|
||||
USD 53.45 53.45 USD J
|
||||
53.45USD fail USD
|
||||
53.45USD 53.45 USD CJ
|
||||
USD53.45 53.45 USD
|
||||
(7.92) USD fail USD
|
||||
(7.926) USD fail USD
|
||||
(7.926 USD) fail 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
|
||||
(USD 7.926) -7.926 USD J
|
||||
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
|
||||
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
|
||||
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 fail USD
|
||||
53.45US Dollars 53.45 USD CJ
|
||||
US Dollar53.45 53.45 USD
|
||||
53.45US Dollar fail USD
|
||||
53.45US Dollar 53.45 USD CJ
|
||||
|
||||
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
|
||||
@ -1058,6 +1255,22 @@ EUR 7.82 7.82 EUR
|
||||
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
|
||||
@ -1110,3 +1323,107 @@ begin
|
||||
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 JKS
|
||||
123 - -123 JKS
|
||||
|
||||
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
|
||||
|
||||
|
@ -48,11 +48,11 @@ public class BigNumberFormatTest extends TestFmwk {
|
||||
DecimalFormatSymbols US = new DecimalFormatSymbols(Locale.US);
|
||||
DecimalFormat f = new DecimalFormat("#,##,###", US);
|
||||
expect(f, new Long(123456789), "12,34,56,789");
|
||||
expectPat(f, "#,##,###");
|
||||
expectPat(f, "#,##,##0");
|
||||
f.applyPattern("#,###");
|
||||
f.setSecondaryGroupingSize(4);
|
||||
expect(f, new Long(123456789), "12,3456,789");
|
||||
expectPat(f, "#,####,###");
|
||||
expectPat(f, "#,####,##0");
|
||||
|
||||
// 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.
|
||||
@ -144,7 +144,7 @@ public class BigNumberFormatTest extends TestFmwk {
|
||||
|
||||
fmt.setFormatWidth(16);
|
||||
// 12 34567890123456
|
||||
expectPat(fmt, "AA*^#,###,##0.00ZZ");
|
||||
expectPat(fmt, "AA*^#####,##0.00ZZ");
|
||||
}
|
||||
|
||||
private void expectPat(DecimalFormat fmt, String exp) {
|
||||
|
@ -20,20 +20,15 @@ import java.text.AttributedCharacterIterator;
|
||||
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.DecimalFormatSymbols;
|
||||
import com.ibm.icu.text.DecimalFormat;
|
||||
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;
|
||||
@ -61,11 +56,13 @@ public class CompactDecimalFormatTest extends TestFmwk {
|
||||
{1234567890123f, "1.2T"},
|
||||
{12345678901234f, "12T"},
|
||||
{123456789012345f, "120T"},
|
||||
{12345678901234567890f, "12000000T"},
|
||||
{12345678901234567890f, "12,000,000T"},
|
||||
};
|
||||
|
||||
Object[][] SerbianTestDataShort = {
|
||||
{1234, "1,2\u00A0\u0445\u0438\u0459."},
|
||||
{1, "1"},
|
||||
{12, "12"},
|
||||
{123, "120"},
|
||||
{12345, "12\u00a0хиљ."},
|
||||
{20789, "21\u00a0хиљ."},
|
||||
{123456, "120\u00a0хиљ."},
|
||||
@ -82,6 +79,9 @@ public class CompactDecimalFormatTest extends TestFmwk {
|
||||
};
|
||||
|
||||
Object[][] SerbianTestDataLong = {
|
||||
{1, "1"},
|
||||
{12, "12"},
|
||||
{123, "120"},
|
||||
{1234, "1,2 хиљаде"},
|
||||
{12345, "12 хиљада"},
|
||||
{21789, "22 хиљаде"},
|
||||
@ -102,6 +102,9 @@ public class CompactDecimalFormatTest extends TestFmwk {
|
||||
};
|
||||
|
||||
Object[][] SerbianTestDataLongNegative = {
|
||||
{-1, "-1"},
|
||||
{-12, "-12"},
|
||||
{-123, "-120"},
|
||||
{-1234, "-1,2 хиљаде"},
|
||||
{-12345, "-12 хиљада"},
|
||||
{-21789, "-22 хиљаде"},
|
||||
@ -122,6 +125,9 @@ public class CompactDecimalFormatTest extends TestFmwk {
|
||||
};
|
||||
|
||||
Object[][] JapaneseTestData = {
|
||||
{1f, "1"},
|
||||
{12f, "12"},
|
||||
{123f, "120"},
|
||||
{1234f, "1200"},
|
||||
{12345f, "1.2万"},
|
||||
{123456f, "12万"},
|
||||
@ -137,9 +143,9 @@ public class CompactDecimalFormatTest extends TestFmwk {
|
||||
};
|
||||
|
||||
Object[][] ChineseCurrencyTestData = {
|
||||
// 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(1f, Currency.getInstance("CNY")), "¥1"},
|
||||
{new CurrencyAmount(12f, Currency.getInstance("CNY")), "¥12"},
|
||||
{new CurrencyAmount(123f, Currency.getInstance("CNY")), "¥120"},
|
||||
{new CurrencyAmount(1234f, Currency.getInstance("CNY")), "¥1.2千"},
|
||||
{new CurrencyAmount(12345f, Currency.getInstance("CNY")), "¥1.2万"},
|
||||
{new CurrencyAmount(123456f, Currency.getInstance("CNY")), "¥12万"},
|
||||
@ -154,6 +160,9 @@ public class CompactDecimalFormatTest extends TestFmwk {
|
||||
{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. €"},
|
||||
@ -168,6 +177,9 @@ public class CompactDecimalFormatTest extends TestFmwk {
|
||||
{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"},
|
||||
@ -183,6 +195,9 @@ public class CompactDecimalFormatTest extends TestFmwk {
|
||||
};
|
||||
|
||||
Object[][] SwahiliTestData = {
|
||||
{1f, "1"},
|
||||
{12f, "12"},
|
||||
{123f, "120"},
|
||||
{1234f, "elfu\u00a01.2"},
|
||||
{12345f, "elfu\u00a012"},
|
||||
{123456f, "elfu\u00A0120"},
|
||||
@ -194,10 +209,13 @@ public class CompactDecimalFormatTest extends TestFmwk {
|
||||
{123456789012f, "B120"},
|
||||
{1234567890123f, "T1.2"},
|
||||
{12345678901234f, "T12"},
|
||||
{12345678901234567890f, "T12000000"},
|
||||
{12345678901234567890f, "T12,000,000"},
|
||||
};
|
||||
|
||||
Object[][] CsTestDataShort = {
|
||||
{1, "1"},
|
||||
{12, "12"},
|
||||
{123, "120"},
|
||||
{1000, "1\u00a0tis."},
|
||||
{1500, "1,5\u00a0tis."},
|
||||
{5000, "5\u00a0tis."},
|
||||
@ -221,6 +239,9 @@ public class CompactDecimalFormatTest extends TestFmwk {
|
||||
};
|
||||
|
||||
Object[][] SwahiliTestDataNegative = {
|
||||
{-1f, "-1"},
|
||||
{-12f, "-12"},
|
||||
{-123f, "-120"},
|
||||
{-1234f, "elfu\u00a0-1.2"},
|
||||
{-12345f, "elfu\u00a0-12"},
|
||||
{-123456f, "elfu\u00A0-120"},
|
||||
@ -232,7 +253,7 @@ public class CompactDecimalFormatTest extends TestFmwk {
|
||||
{-123456789012f, "B-120"},
|
||||
{-1234567890123f, "T-1.2"},
|
||||
{-12345678901234f, "T-12"},
|
||||
{-12345678901234567890f, "T-12000000"},
|
||||
{-12345678901234567890f, "T-12,000,000"},
|
||||
};
|
||||
|
||||
Object[][] TestACoreCompactFormatList = {
|
||||
@ -249,74 +270,78 @@ public class CompactDecimalFormatTest extends TestFmwk {
|
||||
{2000, "2Ks$s"},
|
||||
};
|
||||
|
||||
@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"},
|
||||
});
|
||||
// TODO(sffc): Re-write these tests for the new CompactDecimalFormat pipeline
|
||||
|
||||
Map<String,String[]> currencyAffixes = new HashMap();
|
||||
currencyAffixes.put("one", new String[] {"", "$"});
|
||||
currencyAffixes.put("other", new String[] {"", "$s"});
|
||||
// @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");
|
||||
// }
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
// 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 as default.
|
||||
// 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.
|
||||
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));
|
||||
@ -432,10 +457,11 @@ public class CompactDecimalFormatTest extends TestFmwk {
|
||||
CompactDecimalFormat cdf = CompactDecimalFormat.getInstance(
|
||||
ULocale.ENGLISH, CompactStyle.LONG);
|
||||
BigInteger source_int = new BigInteger("31415926535897932384626433");
|
||||
assertEquals("BigInteger format wrong: ", "31,000,000,000,000 trillion",
|
||||
cdf.setMaximumFractionDigits(0);
|
||||
assertEquals("BigInteger format wrong: ", "31,415,926,535,898 trillion",
|
||||
cdf.format(source_int));
|
||||
BigDecimal source_dec = new BigDecimal(source_int);
|
||||
assertEquals("BigDecimal format wrong: ", "31,000,000,000,000 trillion",
|
||||
assertEquals("BigDecimal format wrong: ", "31,415,926,535,898 trillion",
|
||||
cdf.format(source_dec));
|
||||
}
|
||||
|
||||
@ -548,7 +574,7 @@ public class CompactDecimalFormatTest extends TestFmwk {
|
||||
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);
|
||||
@ -590,4 +616,42 @@ public class CompactDecimalFormatTest extends TestFmwk {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ import com.ibm.icu.util.ULocale;
|
||||
*
|
||||
* @author rocketman
|
||||
*/
|
||||
public class NumberFormatTestData {
|
||||
public class DataDrivenNumberFormatTestData {
|
||||
|
||||
/**
|
||||
* The locale.
|
||||
@ -117,6 +117,7 @@ public class NumberFormatTestData {
|
||||
public String plural = null;
|
||||
public Integer parseIntegerOnly = null;
|
||||
public Integer decimalPatternMatchRequired = null;
|
||||
public Integer parseCaseSensitive = null;
|
||||
public Integer parseNoExponent = null;
|
||||
public String outputCurrency = null;
|
||||
|
||||
@ -409,6 +410,10 @@ public class NumberFormatTestData {
|
||||
parseIntegerOnly = Integer.valueOf(value);
|
||||
}
|
||||
|
||||
public void setParseCaseSensitive(String value) {
|
||||
parseCaseSensitive = Integer.valueOf(value);
|
||||
}
|
||||
|
||||
public void setDecimalPatternMatchRequired(String value) {
|
||||
decimalPatternMatchRequired = Integer.valueOf(value);
|
||||
}
|
||||
@ -482,6 +487,7 @@ public class NumberFormatTestData {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append("{");
|
@ -9,7 +9,9 @@
|
||||
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;
|
||||
|
||||
@ -44,7 +46,8 @@ public class DataDrivenNumberFormatTestUtility {
|
||||
* Subclasses should override.
|
||||
* @param tuple contains the parameters of the format test.
|
||||
*/
|
||||
public String format(NumberFormatTestData tuple) {
|
||||
public String format(DataDrivenNumberFormatTestData tuple) {
|
||||
if (tuple.output != null && tuple.output.equals("fail")) return "fail";
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -54,7 +57,8 @@ public class DataDrivenNumberFormatTestUtility {
|
||||
* Subclasses should override.
|
||||
* @param tuple contains the parameters of the format test.
|
||||
*/
|
||||
public String toPattern(NumberFormatTestData tuple) {
|
||||
public String toPattern(DataDrivenNumberFormatTestData tuple) {
|
||||
if (tuple.output != null && tuple.output.equals("fail")) return "fail";
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -64,7 +68,8 @@ public class DataDrivenNumberFormatTestUtility {
|
||||
* Subclasses should override.
|
||||
* @param tuple contains the parameters of the format test.
|
||||
*/
|
||||
public String parse(NumberFormatTestData tuple) {
|
||||
public String parse(DataDrivenNumberFormatTestData tuple) {
|
||||
if (tuple.output != null && tuple.output.equals("fail")) return "fail";
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -74,7 +79,8 @@ public class DataDrivenNumberFormatTestUtility {
|
||||
* Subclasses should override.
|
||||
* @param tuple contains the parameters of the format test.
|
||||
*/
|
||||
public String parseCurrency(NumberFormatTestData tuple) {
|
||||
public String parseCurrency(DataDrivenNumberFormatTestData tuple) {
|
||||
if (tuple.output != null && tuple.output.equals("fail")) return "fail";
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -84,7 +90,8 @@ public class DataDrivenNumberFormatTestUtility {
|
||||
* Subclasses should override.
|
||||
* @param tuple contains the parameters of the format test.
|
||||
*/
|
||||
public String select(NumberFormatTestData tuple) {
|
||||
public String select(DataDrivenNumberFormatTestData tuple) {
|
||||
if (tuple.output != null && tuple.output.equals("fail")) return "fail";
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -98,7 +105,7 @@ public class DataDrivenNumberFormatTestUtility {
|
||||
private String fileLine = null;
|
||||
private int fileLineNumber = 0;
|
||||
private String fileTestName = "";
|
||||
private NumberFormatTestData tuple = new NumberFormatTestData();
|
||||
private DataDrivenNumberFormatTestData tuple = new DataDrivenNumberFormatTestData();
|
||||
|
||||
/**
|
||||
* Runs all the tests in the data driven test suite against codeUnderTest.
|
||||
@ -107,7 +114,7 @@ public class DataDrivenNumberFormatTestUtility {
|
||||
* @param codeUnderTest the code under test
|
||||
*/
|
||||
|
||||
static void runSuite(
|
||||
public static void runSuite(
|
||||
String fileName, CodeUnderTest codeUnderTest) {
|
||||
new DataDrivenNumberFormatTestUtility(codeUnderTest)
|
||||
.run(fileName, RunMode.SKIP_KNOWN_FAILURES);
|
||||
@ -115,13 +122,14 @@ public class DataDrivenNumberFormatTestUtility {
|
||||
|
||||
/**
|
||||
* Runs every format test in data driven test suite including those
|
||||
* that are known to fail.
|
||||
* that are known to fail. If a test is supposed to fail but actually
|
||||
* passes, an error is printed.
|
||||
*
|
||||
* @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 runFormatSuiteIncludingKnownFailures(
|
||||
public static void runFormatSuiteIncludingKnownFailures(
|
||||
String fileName, CodeUnderTest codeUnderTest) {
|
||||
new DataDrivenNumberFormatTestUtility(codeUnderTest)
|
||||
.run(fileName, RunMode.INCLUDE_KNOWN_FAILURES);
|
||||
@ -166,7 +174,7 @@ public class DataDrivenNumberFormatTestUtility {
|
||||
if (state == 0) {
|
||||
if (fileLine.startsWith("test ")) {
|
||||
fileTestName = fileLine;
|
||||
tuple = new NumberFormatTestData();
|
||||
tuple = new DataDrivenNumberFormatTestData();
|
||||
} else if (fileLine.startsWith("set ")) {
|
||||
if (!setTupleField()) {
|
||||
return;
|
||||
@ -196,19 +204,41 @@ public class DataDrivenNumberFormatTestUtility {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (runMode == RunMode.INCLUDE_KNOWN_FAILURES
|
||||
|| !breaks(codeUnderTestId)) {
|
||||
String errorMessage = isPass(tuple);
|
||||
if (errorMessage != null) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fileLine = null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
showError(e.toString());
|
||||
e.printStackTrace();
|
||||
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));
|
||||
} finally {
|
||||
try {
|
||||
if (in != null) {
|
||||
@ -302,7 +332,7 @@ public class DataDrivenNumberFormatTestUtility {
|
||||
return true;
|
||||
}
|
||||
|
||||
private String isPass(NumberFormatTestData tuple) {
|
||||
private String isPass(DataDrivenNumberFormatTestData tuple) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
if (tuple.format != null && tuple.output != null) {
|
||||
String errorMessage = codeUnderTest.format(tuple);
|
||||
|
@ -298,7 +298,7 @@ public class IntlTestDecimalFormatAPI extends com.ibm.icu.dev.test.TestFmwk
|
||||
DecimalFormat decfmt = new DecimalFormat();
|
||||
MathContext resultICU;
|
||||
|
||||
MathContext comp1 = new MathContext(0, MathContext.PLAIN);
|
||||
MathContext comp1 = new MathContext(0, MathContext.PLAIN, false, MathContext.ROUND_HALF_EVEN);
|
||||
resultICU = decfmt.getMathContextICU();
|
||||
if ((comp1.getDigits() != resultICU.getDigits()) ||
|
||||
(comp1.getForm() != resultICU.getForm()) ||
|
||||
@ -309,7 +309,7 @@ public class IntlTestDecimalFormatAPI extends com.ibm.icu.dev.test.TestFmwk
|
||||
" / expected: " + comp1.toString());
|
||||
}
|
||||
|
||||
MathContext comp2 = new MathContext(5, MathContext.ENGINEERING);
|
||||
MathContext comp2 = new MathContext(5, MathContext.ENGINEERING, false, MathContext.ROUND_HALF_EVEN);
|
||||
decfmt.setMathContextICU(comp2);
|
||||
resultICU = decfmt.getMathContextICU();
|
||||
if ((comp2.getDigits() != resultICU.getDigits()) ||
|
||||
|
@ -228,7 +228,7 @@ public class IntlTestDecimalFormatAPIC extends com.ibm.icu.dev.test.TestFmwk {
|
||||
s2 = pat.toPattern();
|
||||
logln("Extracted pattern is " + s2);
|
||||
if (!s2.equals(p1)) {
|
||||
errln("ERROR: toPattern() result did not match pattern applied");
|
||||
errln("ERROR: toPattern() result did not match pattern applied: " + p1 + " vs " + s2);
|
||||
}
|
||||
|
||||
String p2 = new String("#,##0.0# FF;(#,##0.0# FF)");
|
||||
@ -237,9 +237,7 @@ public class IntlTestDecimalFormatAPIC extends com.ibm.icu.dev.test.TestFmwk {
|
||||
String s3;
|
||||
s3 = pat.toLocalizedPattern();
|
||||
logln("Extracted pattern is " + s3);
|
||||
if (!s3.equals(p2)) {
|
||||
errln("ERROR: toLocalizedPattern() result did not match pattern applied");
|
||||
}
|
||||
assertEquals("ERROR: toLocalizedPattern() result did not match pattern applied", p2, s3);
|
||||
|
||||
// ======= Test getStaticClassID()
|
||||
|
||||
|
@ -124,7 +124,7 @@ public class IntlTestDecimalFormatSymbolsC extends com.ibm.icu.dev.test.TestFmwk
|
||||
sym.setPercent('P');
|
||||
verify(34.5, "00 %", sym, "3450 P");
|
||||
sym.setCurrencySymbol("D");
|
||||
verify(34.5, "\u00a4##.##", sym, "D34.5");
|
||||
verify(34.5, "\u00a4##.##", sym, "D34.50");
|
||||
sym.setGroupingSeparator('|');
|
||||
verify(3456.5, "0,000.##", sym, "3|456S5");
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ import java.util.TreeMap;
|
||||
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;
|
||||
@ -2035,6 +2036,13 @@ public class MeasureUnitTest extends TestFmwk {
|
||||
}
|
||||
}
|
||||
|
||||
@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() {
|
||||
@ -2528,6 +2536,8 @@ public class MeasureUnitTest extends TestFmwk {
|
||||
|
||||
public static class MeasureFormatHandler implements SerializableTestUtility.Handler
|
||||
{
|
||||
FormatHandler.NumberFormatHandler nfh = new FormatHandler.NumberFormatHandler();
|
||||
|
||||
@Override
|
||||
public Object[] getTestObjects()
|
||||
{
|
||||
@ -2547,8 +2557,7 @@ public class MeasureUnitTest extends TestFmwk {
|
||||
MeasureFormat b1 = (MeasureFormat) b;
|
||||
return a1.getLocale().equals(b1.getLocale())
|
||||
&& a1.getWidth().equals(b1.getWidth())
|
||||
&& a1.getNumberFormat().equals(b1.getNumberFormat())
|
||||
;
|
||||
&& nfh.hasSameBehavior(a1.getNumberFormat(), b1.getNumberFormat());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,434 @@
|
||||
// © 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.dev.test.number.ShanesDataDrivenTestUtility;
|
||||
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() {
|
||||
ShanesDataDrivenTestUtility.run();
|
||||
}
|
||||
}
|
@ -176,17 +176,13 @@ public class NumberFormatRegressionTest extends com.ibm.icu.dev.test.TestFmwk {
|
||||
byte[][] contents = NumberFormatSerialTestData.getContent();
|
||||
double data = 1234.56;
|
||||
String[] expected = {
|
||||
"1,234.56", "$1,234.56", "123,456%", "1.23456E3"};
|
||||
"1,234.56", "$1,234.56", "1.23456E3", "1,234.56"};
|
||||
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);
|
||||
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");
|
||||
}
|
||||
assertEquals("Deserialization new version should read old version", expected[i], result);
|
||||
} catch (Exception e) {
|
||||
warnln("FAIL: " + e.getMessage());
|
||||
}
|
||||
@ -385,4 +381,14 @@ public class NumberFormatRegressionTest extends com.ibm.icu.dev.test.TestFmwk {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
@ -10,13 +10,9 @@
|
||||
package com.ibm.icu.dev.test.format;
|
||||
|
||||
public class NumberFormatSerialTestData {
|
||||
//get Content
|
||||
public static byte[][] getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
//NumberFormat.getInstance(Locale.US)
|
||||
static byte[] generalInstance = new byte[]{
|
||||
static byte[] generalInstance() { return 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,
|
||||
@ -85,10 +81,10 @@ public class NumberFormatSerialTestData {
|
||||
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.getCurrencyInstance(Locale.US)
|
||||
static byte[] currencyInstance = new byte[]{
|
||||
static byte[] currencyInstance () { return 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,
|
||||
@ -158,82 +154,10 @@ public class NumberFormatSerialTestData {
|
||||
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.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[]{
|
||||
static byte[] scientificInstance() { return 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,
|
||||
@ -302,7 +226,295 @@ public class NumberFormatSerialTestData {
|
||||
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,
|
||||
};
|
||||
};};
|
||||
|
||||
final static byte[][] content = {generalInstance, currencyInstance, percentInstance, scientificInstance};
|
||||
static byte[] icu58Latest() { return 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, 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() { return 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, 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
|
||||
};};
|
||||
|
||||
static byte[][] getContent() {
|
||||
return new byte[][] {
|
||||
generalInstance(),
|
||||
currencyInstance(),
|
||||
scientificInstance(),
|
||||
icu58Latest(),
|
||||
newFromPattern()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -18,12 +18,12 @@ rt: "0.###" 1.0 "1"
|
||||
# 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
|
||||
@ -68,7 +68,8 @@ pat: "##,@@##" "#,@@##"
|
||||
pat: "##@@##" "@@##"
|
||||
|
||||
pat: "@@.@@" err # decimal sep. disallowed in sig. digits
|
||||
pat: "@#@" err # only one cluster of sig. digits
|
||||
# The new pattern parser treats this the same as "@@#"
|
||||
#pat: "@#@" err # only one cluster of sig. digits
|
||||
pat: "@@0" err # either @ or 0, not both
|
||||
|
||||
# NumberRegression/Test4140009
|
||||
|
@ -28,7 +28,6 @@ package com.ibm.icu.dev.test.format;
|
||||
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;
|
||||
@ -424,24 +423,24 @@ public class NumberRegressionTests extends TestFmwk {
|
||||
|
||||
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));
|
||||
@ -1043,9 +1042,15 @@ public class NumberRegressionTests extends TestFmwk {
|
||||
// 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.
|
||||
fmt2.setMaximumFractionDigits(fmt1.getMaximumFractionDigits());
|
||||
fmt2.setMinimumFractionDigits(fmt1.getMinimumFractionDigits());
|
||||
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());
|
||||
}
|
||||
|
||||
String result2 = fmt2.format(1.111);
|
||||
|
||||
@ -1246,7 +1251,9 @@ public class NumberRegressionTests extends TestFmwk {
|
||||
logln("Applying pattern \"" + pattern + "\"");
|
||||
sdf.applyPattern(pattern);
|
||||
int minIntDig = sdf.getMinimumIntegerDigits();
|
||||
if (minIntDig != 0) {
|
||||
// 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) {
|
||||
errln("Test failed");
|
||||
errln(" Minimum integer digits : " + minIntDig);
|
||||
errln(" new pattern: " + sdf.toPattern());
|
||||
@ -1295,9 +1302,7 @@ public class NumberRegressionTests extends TestFmwk {
|
||||
e.printStackTrace();
|
||||
}
|
||||
logln("The string " + s + " parsed as " + n);
|
||||
if (n.doubleValue() != dbl) {
|
||||
errln("Round trip failure");
|
||||
}
|
||||
assertEquals("Round trip failure", dbl, n.doubleValue());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1379,15 +1384,15 @@ public class NumberRegressionTests extends TestFmwk {
|
||||
@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]);
|
||||
@ -1426,98 +1431,6 @@ public class NumberRegressionTests extends TestFmwk {
|
||||
}
|
||||
}
|
||||
|
||||
@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
|
||||
@ -1627,6 +1540,7 @@ public class NumberRegressionTests extends TestFmwk {
|
||||
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() + '"');
|
||||
@ -1644,14 +1558,22 @@ public class NumberRegressionTests extends TestFmwk {
|
||||
"\" -> \"" + s2 + '"'+ " in locale "+df.getLocale(ULocale.ACTUAL_LOCALE));
|
||||
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
// 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());
|
||||
|
||||
}catch(IllegalArgumentException ex){
|
||||
errln(ex.getMessage()+" for locale "+ df.getLocale(ULocale.ACTUAL_LOCALE));
|
||||
throw new AssertionError("For locale " + avail[i], ex);
|
||||
}
|
||||
|
||||
|
||||
@ -1682,7 +1604,7 @@ public class NumberRegressionTests extends TestFmwk {
|
||||
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();
|
||||
@ -1713,7 +1635,7 @@ public class NumberRegressionTests extends TestFmwk {
|
||||
String str = Long.toString(DATA[i]);
|
||||
for (int m = 1; m <= 100; m++) {
|
||||
fmt.setMultiplier(m);
|
||||
long n = ((Number) fmt.parse(str)).longValue();
|
||||
long n = fmt.parse(str).longValue();
|
||||
if (n > 0 != DATA[i] > 0) {
|
||||
errln("\"" + str + "\" parse(x " + fmt.getMultiplier() +
|
||||
") => " + n);
|
||||
@ -1916,21 +1838,27 @@ class MyNumberFormat extends NumberFormat {
|
||||
* For serialization
|
||||
*/
|
||||
private static final long serialVersionUID = 1251303884737169952L;
|
||||
@Override
|
||||
public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) {
|
||||
return new StringBuffer("");
|
||||
}
|
||||
@Override
|
||||
public StringBuffer format(long number,StringBuffer toAppendTo, FieldPosition pos) {
|
||||
return new StringBuffer("");
|
||||
}
|
||||
@Override
|
||||
public Number parse(String text, ParsePosition parsePosition) {
|
||||
return new Integer(0);
|
||||
}
|
||||
@Override
|
||||
public StringBuffer format(java.math.BigDecimal number, StringBuffer toAppendTo, FieldPosition pos) {
|
||||
return new StringBuffer("");
|
||||
}
|
||||
@Override
|
||||
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) {
|
||||
return new StringBuffer("");
|
||||
}
|
||||
|
@ -883,8 +883,9 @@ public class TestMessageFormat extends com.ibm.icu.dev.test.TestFmwk {
|
||||
errln("parsed argument " + parsedArgs[0] + " != " + num);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
errln("parse of '" + result + " returned exception: " + e.getMessage());
|
||||
catch (ParseException e) {
|
||||
errln("parse of '" + result + "' returned exception: "
|
||||
+ e.getMessage() + " " + e.getErrorOffset());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,126 @@
|
||||
// © 2017 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package com.ibm.icu.dev.test.number;
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,475 @@
|
||||
// © 2017 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package com.ibm.icu.dev.test.number;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
@ -0,0 +1,171 @@
|
||||
// © 2017 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package com.ibm.icu.dev.test.number;
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
// © 2017 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package com.ibm.icu.dev.test.number;
|
||||
|
||||
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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,331 @@
|
||||
// © 2017 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package com.ibm.icu.dev.test.number;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,134 @@
|
||||
// © 2017 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package com.ibm.icu.dev.test.number;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
@ -0,0 +1,345 @@
|
||||
// © 2017 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package com.ibm.icu.dev.test.number;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.text.ParseException;
|
||||
import java.text.ParsePosition;
|
||||
|
||||
import com.ibm.icu.dev.test.format.DataDrivenNumberFormatTestData;
|
||||
import com.ibm.icu.dev.test.format.DataDrivenNumberFormatTestUtility;
|
||||
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 ShanesDataDrivenTestUtility extends CodeUnderTest {
|
||||
static final String dataPath =
|
||||
"../../../icu4j-core-tests/src/com/ibm/icu/dev/data/numberformattestspecification.txt";
|
||||
|
||||
public static void run() {
|
||||
CodeUnderTest tester = new ShanesDataDrivenTestUtility();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -1106,7 +1106,14 @@ public class FormatHandler
|
||||
NumberFormat format_b = (NumberFormat) b;
|
||||
double number = 1234.56;
|
||||
|
||||
return format_a.format(number).equals(format_b.format(number));
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1710,7 +1717,17 @@ public class FormatHandler
|
||||
char chars_a[] = getCharSymbols(dfs_a);
|
||||
char chars_b[] = getCharSymbols(dfs_b);
|
||||
|
||||
return SerializableTestUtility.compareStrings(strings_a, strings_b) && SerializableTestUtility.compareChars(chars_a, chars_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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,6 +30,7 @@ import java.util.Locale;
|
||||
|
||||
import com.ibm.icu.dev.test.format.MeasureUnitTest;
|
||||
import com.ibm.icu.dev.test.format.PluralRulesTest;
|
||||
import com.ibm.icu.dev.test.number.PropertiesTest;
|
||||
import com.ibm.icu.impl.JavaTimeZone;
|
||||
import com.ibm.icu.impl.OlsonTimeZone;
|
||||
import com.ibm.icu.impl.TimeZoneAdapter;
|
||||
@ -827,6 +828,7 @@ public class SerializableTestUtility {
|
||||
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());
|
||||
@ -925,6 +927,11 @@ public class SerializableTestUtility {
|
||||
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;
|
||||
|
@ -8,6 +8,7 @@
|
||||
*/
|
||||
package com.ibm.icu.dev.test.util;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.junit.Test;
|
||||
@ -25,6 +26,11 @@ public class TextTrieMapTest extends TestFmwk {
|
||||
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);
|
||||
|
||||
@ -49,7 +55,11 @@ public class TextTrieMapTest extends TestFmwk {
|
||||
{"W", WED},
|
||||
{"T", THU},
|
||||
{"F", FRI},
|
||||
{"S", SAT}
|
||||
{"S", SAT},
|
||||
{"L📺", SUP1}, // L, 0xD83D, 0xDCFA
|
||||
{"L📺1", SUP2}, // L, 0xD83D, 0xDCFA, 1
|
||||
{"L📻", SUP3}, // L, 0xD83D, 0xDCFB
|
||||
{"L🃏", SUP4}, // L, 0xD83C, 0xDCCF
|
||||
};
|
||||
|
||||
private static final Object[][] TESTCASES = {
|
||||
@ -62,7 +72,70 @@ public class TextTrieMapTest extends TestFmwk {
|
||||
{"TEST", new Object[]{TUE, THU}, new Object[]{TUE, THU}},
|
||||
{"SUN", new Object[]{SUN, SAT}, SUN},
|
||||
{"super", null, SUN},
|
||||
{"NO", null, null}
|
||||
{"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🃏"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Test
|
||||
@ -76,7 +149,7 @@ public class TextTrieMapTest extends TestFmwk {
|
||||
logln("Test for get(String)");
|
||||
for (int i = 0; i < TESTCASES.length; i++) {
|
||||
itr = map.get((String)TESTCASES[i][0]);
|
||||
checkResult(itr, TESTCASES[i][1]);
|
||||
checkResult("get(String) case " + i, itr, TESTCASES[i][1]);
|
||||
}
|
||||
|
||||
logln("Test for get(String, int)");
|
||||
@ -88,7 +161,14 @@ public class TextTrieMapTest extends TestFmwk {
|
||||
}
|
||||
textBuf.append(TESTCASES[i][0]);
|
||||
itr = map.get(textBuf.toString(), i);
|
||||
checkResult(itr, TESTCASES[i][1]);
|
||||
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);
|
||||
}
|
||||
|
||||
// Add duplicated entry
|
||||
@ -98,7 +178,7 @@ public class TextTrieMapTest extends TestFmwk {
|
||||
|
||||
// Make sure the all entries are returned
|
||||
itr = map.get("Sunday");
|
||||
checkResult(itr, new Object[]{FOO, SUN});
|
||||
checkResult("Get Sunday", itr, new Object[]{FOO, SUN});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -112,7 +192,7 @@ public class TextTrieMapTest extends TestFmwk {
|
||||
logln("Test for get(String)");
|
||||
for (int i = 0; i < TESTCASES.length; i++) {
|
||||
itr = map.get((String)TESTCASES[i][0]);
|
||||
checkResult(itr, TESTCASES[i][2]);
|
||||
checkResult("get(String) case " + i, itr, TESTCASES[i][2]);
|
||||
}
|
||||
|
||||
logln("Test for get(String, int)");
|
||||
@ -124,7 +204,14 @@ public class TextTrieMapTest extends TestFmwk {
|
||||
}
|
||||
textBuf.append(TESTCASES[i][0]);
|
||||
itr = map.get(textBuf.toString(), i);
|
||||
checkResult(itr, TESTCASES[i][2]);
|
||||
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);
|
||||
}
|
||||
|
||||
// Add duplicated entry
|
||||
@ -134,7 +221,55 @@ public class TextTrieMapTest extends TestFmwk {
|
||||
|
||||
// Make sure the all entries are returned
|
||||
itr = map.get("Sunday");
|
||||
checkResult(itr, new Object[]{SUN, FOO, BAR});
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean eql(Object o1, Object o2) {
|
||||
@ -147,10 +282,13 @@ public class TextTrieMapTest extends TestFmwk {
|
||||
return o1.equals(o2);
|
||||
}
|
||||
|
||||
private void checkResult(Iterator itr, Object expected) {
|
||||
private void checkResult(String memo, Iterator itr, Object expected) {
|
||||
if (itr == null) {
|
||||
if (expected != null) {
|
||||
errln("FAIL: Empty results - Expected: " + expected);
|
||||
String expectedStr = (expected instanceof Object[])
|
||||
? Arrays.toString((Object[]) expected)
|
||||
: expected.toString();
|
||||
errln("FAIL: Empty results: " + memo + ": Expected: " + expectedStr);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -2,17 +2,17 @@
|
||||
<launchConfiguration type="org.eclipse.ant.AntLaunchConfigurationType">
|
||||
<booleanAttribute key="org.eclipse.ant.ui.DEFAULT_VM_INSTALL" value="false"/>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
|
||||
<listEntry value="/icu4j-translit-tests/build.xml"/>
|
||||
<listEntry value="/icu4j-core-tests"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
|
||||
<listEntry value="1"/>
|
||||
<listEntry value="4"/>
|
||||
</listAttribute>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="org.eclipse.ant.ui.AntClasspathProvider"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="icu4j-core-tests"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.ant.ui.AntClasspathProvider"/>
|
||||
<mapAttribute key="org.eclipse.ui.externaltools.ATTR_ANT_PROPERTIES">
|
||||
<mapEntry key="eclipse.running" value="true"/>
|
||||
<mapEntry key="build.compiler" value="org.eclipse.jdt.core.JDTCompilerAdapter"/>
|
||||
<mapEntry key="eclipse.running" value="true"/>
|
||||
</mapAttribute>
|
||||
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_ANT_PROPERTY_FILES" value="${workspace_loc:/icu4j-shared/build/locations-eclipse.properties},"/>
|
||||
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/icu4j-translit-tests/build.xml}"/>
|
||||
|
Loading…
Reference in New Issue
Block a user