ICU-13177 Adding new number formatting code to repository.
X-SVN-Rev: 40315
This commit is contained in:
parent
8540468f28
commit
f5af411b7f
@ -0,0 +1,447 @@
|
||||
// © 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.formatters.PaddingFormat.PadPosition;
|
||||
|
||||
import newapi.impl.AffixPatternProvider;
|
||||
|
||||
/** Implements a recursive descent parser for decimal format patterns. */
|
||||
public class LdmlPatternInfo {
|
||||
|
||||
public static PatternParseResult parse(String patternString) {
|
||||
ParserState state = new ParserState(patternString);
|
||||
PatternParseResult result = new PatternParseResult(patternString);
|
||||
consumePattern(state, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* An internal, intermediate data structure used for storing parse results before they are
|
||||
* finalized into a DecimalFormatPattern.Builder.
|
||||
*/
|
||||
public static class PatternParseResult implements AffixPatternProvider {
|
||||
public String pattern;
|
||||
public LdmlPatternInfo.SubpatternParseResult positive;
|
||||
public LdmlPatternInfo.SubpatternParseResult negative;
|
||||
|
||||
private PatternParseResult(String pattern) {
|
||||
this.pattern = pattern;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char charAt(int flags, int index) {
|
||||
long endpoints = getEndpoints(flags);
|
||||
int left = (int) (endpoints & 0xffffffff);
|
||||
int right = (int) (endpoints >>> 32);
|
||||
if (index < 0 || index >= right - left) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
return pattern.charAt(left + index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int length(int flags) {
|
||||
return getLengthFromEndpoints(getEndpoints(flags));
|
||||
}
|
||||
|
||||
public static int getLengthFromEndpoints(long endpoints) {
|
||||
int left = (int) (endpoints & 0xffffffff);
|
||||
int right = (int) (endpoints >>> 32);
|
||||
return right - left;
|
||||
}
|
||||
|
||||
public String getString(int flags) {
|
||||
long endpoints = getEndpoints(flags);
|
||||
int left = (int) (endpoints & 0xffffffff);
|
||||
int right = (int) (endpoints >>> 32);
|
||||
if (left == right) {
|
||||
return "";
|
||||
}
|
||||
return pattern.substring(left, right);
|
||||
}
|
||||
|
||||
private long getEndpoints(int flags) {
|
||||
boolean prefix = (flags & Flags.PREFIX) != 0;
|
||||
boolean isNegative = (flags & Flags.NEGATIVE_SUBPATTERN) != 0;
|
||||
boolean padding = (flags & Flags.PADDING) != 0;
|
||||
if (isNegative && padding) {
|
||||
return negative.paddingEndpoints;
|
||||
} else if (padding) {
|
||||
return positive.paddingEndpoints;
|
||||
} else if (prefix && isNegative) {
|
||||
return negative.prefixEndpoints;
|
||||
} else if (prefix) {
|
||||
return positive.prefixEndpoints;
|
||||
} else if (isNegative) {
|
||||
return negative.suffixEndpoints;
|
||||
} else {
|
||||
return positive.suffixEndpoints;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean positiveHasPlusSign() {
|
||||
return positive.hasPlusSign;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNegativeSubpattern() {
|
||||
return negative != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean negativeHasMinusSign() {
|
||||
return negative.hasMinusSign;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCurrencySign() {
|
||||
return positive.hasCurrencySign || (negative != null && negative.hasCurrencySign);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsSymbolType(int type) {
|
||||
return AffixPatternUtils.containsType(pattern, type);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SubpatternParseResult {
|
||||
public long groupingSizes = 0x0000ffffffff0000L;
|
||||
public int minimumIntegerDigits = 0;
|
||||
public int totalIntegerDigits = 0;
|
||||
public int minimumFractionDigits = 0;
|
||||
public int maximumFractionDigits = 0;
|
||||
public int minimumSignificantDigits = 0;
|
||||
public int maximumSignificantDigits = 0;
|
||||
public boolean hasDecimal = false;
|
||||
public int paddingWidth = 0;
|
||||
public PadPosition paddingLocation = null;
|
||||
public FormatQuantity4 rounding = null;
|
||||
public boolean exponentShowPlusSign = false;
|
||||
public int exponentDigits = 0;
|
||||
public boolean hasPercentSign = false;
|
||||
public boolean hasPerMilleSign = false;
|
||||
public boolean hasCurrencySign = false;
|
||||
public boolean hasMinusSign = false;
|
||||
public boolean hasPlusSign = false;
|
||||
|
||||
public long prefixEndpoints = 0;
|
||||
public long suffixEndpoints = 0;
|
||||
public long paddingEndpoints = 0;
|
||||
}
|
||||
|
||||
/** 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("Malformed pattern for ICU DecimalFormat: \"");
|
||||
sb.append(pattern);
|
||||
sb.append("\": ");
|
||||
sb.append(message);
|
||||
sb.append(" at position ");
|
||||
sb.append(offset);
|
||||
return new IllegalArgumentException(sb.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static void consumePattern(
|
||||
LdmlPatternInfo.ParserState state, LdmlPatternInfo.PatternParseResult result) {
|
||||
// pattern := subpattern (';' subpattern)?
|
||||
result.positive = new SubpatternParseResult();
|
||||
consumeSubpattern(state, result.positive);
|
||||
if (state.peek() == ';') {
|
||||
state.next(); // consume the ';'
|
||||
// Don't consume the negative subpattern if it is empty (trailing ';')
|
||||
if (state.peek() != -1) {
|
||||
result.negative = new SubpatternParseResult();
|
||||
consumeSubpattern(state, result.negative);
|
||||
}
|
||||
}
|
||||
if (state.peek() != -1) {
|
||||
throw state.toParseException("Found unquoted special character");
|
||||
}
|
||||
}
|
||||
|
||||
private static void consumeSubpattern(
|
||||
LdmlPatternInfo.ParserState state, LdmlPatternInfo.SubpatternParseResult result) {
|
||||
// subpattern := literals? number exponent? literals?
|
||||
consumePadding(state, result, PadPosition.BEFORE_PREFIX);
|
||||
result.prefixEndpoints = consumeAffix(state, result);
|
||||
consumePadding(state, result, PadPosition.AFTER_PREFIX);
|
||||
consumeFormat(state, result);
|
||||
consumeExponent(state, result);
|
||||
consumePadding(state, result, PadPosition.BEFORE_SUFFIX);
|
||||
result.suffixEndpoints = consumeAffix(state, result);
|
||||
consumePadding(state, result, PadPosition.AFTER_SUFFIX);
|
||||
}
|
||||
|
||||
private static void consumePadding(
|
||||
LdmlPatternInfo.ParserState state,
|
||||
LdmlPatternInfo.SubpatternParseResult result,
|
||||
PadPosition paddingLocation) {
|
||||
if (state.peek() != '*') {
|
||||
return;
|
||||
}
|
||||
result.paddingLocation = paddingLocation;
|
||||
state.next(); // consume the '*'
|
||||
result.paddingEndpoints |= state.offset;
|
||||
consumeLiteral(state);
|
||||
result.paddingEndpoints |= ((long) state.offset) << 32;
|
||||
}
|
||||
|
||||
private static long consumeAffix(
|
||||
LdmlPatternInfo.ParserState state, LdmlPatternInfo.SubpatternParseResult result) {
|
||||
// literals := { literal }
|
||||
long endpoints = state.offset;
|
||||
outer:
|
||||
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
|
||||
break outer;
|
||||
|
||||
case '%':
|
||||
result.hasPercentSign = true;
|
||||
break;
|
||||
|
||||
case '‰':
|
||||
result.hasPerMilleSign = true;
|
||||
break;
|
||||
|
||||
case '¤':
|
||||
result.hasCurrencySign = true;
|
||||
break;
|
||||
|
||||
case '-':
|
||||
result.hasMinusSign = true;
|
||||
break;
|
||||
|
||||
case '+':
|
||||
result.hasPlusSign = true;
|
||||
break;
|
||||
}
|
||||
consumeLiteral(state);
|
||||
}
|
||||
endpoints |= ((long) state.offset) << 32;
|
||||
return endpoints;
|
||||
}
|
||||
|
||||
private static void consumeLiteral(LdmlPatternInfo.ParserState state) {
|
||||
if (state.peek() == -1) {
|
||||
throw state.toParseException("Expected unquoted literal but found EOL");
|
||||
} else if (state.peek() == '\'') {
|
||||
state.next(); // consume the starting quote
|
||||
while (state.peek() != '\'') {
|
||||
if (state.peek() == -1) {
|
||||
throw state.toParseException("Expected quoted literal but found EOL");
|
||||
} else {
|
||||
state.next(); // consume a quoted character
|
||||
}
|
||||
}
|
||||
state.next(); // consume the ending quote
|
||||
} else {
|
||||
// consume a non-quoted literal character
|
||||
state.next();
|
||||
}
|
||||
}
|
||||
|
||||
private static void consumeFormat(
|
||||
LdmlPatternInfo.ParserState state, LdmlPatternInfo.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(
|
||||
LdmlPatternInfo.ParserState state, LdmlPatternInfo.SubpatternParseResult result) {
|
||||
boolean seenSignificantDigitMarker = false;
|
||||
boolean seenDigit = false;
|
||||
|
||||
outer:
|
||||
while (true) {
|
||||
switch (state.peek()) {
|
||||
case ',':
|
||||
result.paddingWidth += 1;
|
||||
result.groupingSizes <<= 16;
|
||||
break;
|
||||
|
||||
case '#':
|
||||
if (seenDigit) throw state.toParseException("# cannot follow 0 before decimal point");
|
||||
result.paddingWidth += 1;
|
||||
result.groupingSizes += 1;
|
||||
result.totalIntegerDigits += (seenSignificantDigitMarker ? 0 : 1);
|
||||
// no change to result.minimumIntegerDigits
|
||||
// no change to result.minimumSignificantDigits
|
||||
result.maximumSignificantDigits += (seenSignificantDigitMarker ? 1 : 0);
|
||||
if (result.rounding != null) {
|
||||
result.rounding.appendDigit((byte) 0, 0, true);
|
||||
}
|
||||
break;
|
||||
|
||||
case '@':
|
||||
seenSignificantDigitMarker = true;
|
||||
if (seenDigit) throw state.toParseException("Cannot mix 0 and @");
|
||||
result.paddingWidth += 1;
|
||||
result.groupingSizes += 1;
|
||||
result.totalIntegerDigits += 1;
|
||||
// no change to result.minimumIntegerDigits
|
||||
result.minimumSignificantDigits += 1;
|
||||
result.maximumSignificantDigits += 1;
|
||||
if (result.rounding != null) {
|
||||
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("Cannot mix @ and 0");
|
||||
// TODO: Crash here if we've seen the significant digit marker? See NumberFormatTestCases.txt
|
||||
result.paddingWidth += 1;
|
||||
result.groupingSizes += 1;
|
||||
result.totalIntegerDigits += 1;
|
||||
result.minimumIntegerDigits += 1;
|
||||
// no change to result.minimumSignificantDigits
|
||||
// no change to result.maximumSignificantDigits
|
||||
if (state.peek() != '0' && result.rounding == null) {
|
||||
result.rounding = new FormatQuantity4();
|
||||
}
|
||||
if (result.rounding != null) {
|
||||
result.rounding.appendDigit((byte) (state.peek() - '0'), 0, true);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break outer;
|
||||
}
|
||||
state.next(); // consume the symbol
|
||||
}
|
||||
|
||||
// Disallow patterns with a trailing ',' or with two ',' next to each other
|
||||
short grouping1 = (short) (result.groupingSizes & 0xffff);
|
||||
short grouping2 = (short) ((result.groupingSizes >>> 16) & 0xffff);
|
||||
short grouping3 = (short) ((result.groupingSizes >>> 32) & 0xffff);
|
||||
if (grouping1 == 0 && grouping2 != -1) {
|
||||
throw state.toParseException("Trailing grouping separator is invalid");
|
||||
}
|
||||
if (grouping2 == 0 && grouping3 != -1) {
|
||||
throw state.toParseException("Grouping width of zero is invalid");
|
||||
}
|
||||
}
|
||||
|
||||
private static void consumeFractionFormat(
|
||||
LdmlPatternInfo.ParserState state, LdmlPatternInfo.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 {
|
||||
if (result.rounding == null) {
|
||||
result.rounding = new FormatQuantity4();
|
||||
}
|
||||
result.rounding.appendDigit((byte) (state.peek() - '0'), zeroCounter, false);
|
||||
zeroCounter = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
state.next(); // consume the symbol
|
||||
}
|
||||
}
|
||||
|
||||
private static void consumeExponent(
|
||||
LdmlPatternInfo.ParserState state, LdmlPatternInfo.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++;
|
||||
}
|
||||
}
|
||||
}
|
684
icu4j/main/classes/core/src/newapi/NumberFormatter.java
Normal file
684
icu4j/main/classes/core/src/newapi/NumberFormatter.java
Normal file
@ -0,0 +1,684 @@
|
||||
// © 2017 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package newapi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.math.RoundingMode;
|
||||
import java.text.AttributedCharacterIterator;
|
||||
import java.text.FieldPosition;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
import com.ibm.icu.impl.number.FormatQuantityBCD;
|
||||
import com.ibm.icu.impl.number.NumberStringBuilder;
|
||||
import com.ibm.icu.impl.number.formatters.PaddingFormat.PadPosition;
|
||||
import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
|
||||
import com.ibm.icu.text.DecimalFormatSymbols;
|
||||
import com.ibm.icu.text.MeasureFormat.FormatWidth;
|
||||
import com.ibm.icu.text.NumberingSystem;
|
||||
import com.ibm.icu.text.PluralRules.IFixedDecimal;
|
||||
import com.ibm.icu.util.Currency;
|
||||
import com.ibm.icu.util.Currency.CurrencyUsage;
|
||||
import com.ibm.icu.util.CurrencyAmount;
|
||||
import com.ibm.icu.util.ICUUncheckedIOException;
|
||||
import com.ibm.icu.util.Measure;
|
||||
import com.ibm.icu.util.MeasureUnit;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
|
||||
import newapi.impl.GroupingImpl;
|
||||
import newapi.impl.IntegerWidthImpl;
|
||||
import newapi.impl.MicroProps;
|
||||
import newapi.impl.NotationImpl.NotationCompactImpl;
|
||||
import newapi.impl.NotationImpl.NotationScientificImpl;
|
||||
import newapi.impl.NumberFormatterImpl;
|
||||
import newapi.impl.PaddingImpl;
|
||||
import newapi.impl.RoundingImpl.RoundingImplCurrency;
|
||||
import newapi.impl.RoundingImpl.RoundingImplFraction;
|
||||
import newapi.impl.RoundingImpl.RoundingImplIncrement;
|
||||
import newapi.impl.RoundingImpl.RoundingImplInfinity;
|
||||
import newapi.impl.RoundingImpl.RoundingImplSignificant;
|
||||
|
||||
public final class NumberFormatter {
|
||||
|
||||
public interface IRounding {
|
||||
public BigDecimal round(BigDecimal input);
|
||||
}
|
||||
|
||||
public interface IGrouping {
|
||||
public boolean groupAtPosition(int position, BigDecimal input);
|
||||
}
|
||||
|
||||
// This could possibly be combined into MeasureFormat.FormatWidth
|
||||
public static enum CurrencyDisplay {
|
||||
SYMBOL, // ¤
|
||||
ISO_4217, // ¤¤
|
||||
DISPLAY_NAME, // ¤¤¤
|
||||
SYMBOL_NARROW, // ¤¤¤¤
|
||||
HIDDEN, // uses currency rounding and formatting but omits the currency symbol
|
||||
// TODO: For hidden, what to do if currency symbol appears in the middle, as in Portugal ?
|
||||
}
|
||||
|
||||
public static enum DecimalMarkDisplay {
|
||||
AUTO,
|
||||
ALWAYS_SHOWN,
|
||||
}
|
||||
|
||||
public static enum SignDisplay {
|
||||
AUTO,
|
||||
ALWAYS_SHOWN,
|
||||
NEVER_SHOWN,
|
||||
}
|
||||
|
||||
public static class UnlocalizedNumberFormatter {
|
||||
|
||||
public UnlocalizedNumberFormatter notation(Notation notation) {
|
||||
throw new AssertionError("See NumberFormatterImpl");
|
||||
}
|
||||
|
||||
public UnlocalizedNumberFormatter unit(MeasureUnit unit) {
|
||||
throw new AssertionError("See NumberFormatterImpl");
|
||||
}
|
||||
|
||||
public UnlocalizedNumberFormatter rounding(IRounding rounding) {
|
||||
throw new AssertionError("See NumberFormatterImpl");
|
||||
}
|
||||
|
||||
public UnlocalizedNumberFormatter grouping(IGrouping grouping) {
|
||||
throw new AssertionError("See NumberFormatterImpl");
|
||||
}
|
||||
|
||||
public UnlocalizedNumberFormatter padding(Padding padding) {
|
||||
throw new AssertionError("See NumberFormatterImpl");
|
||||
}
|
||||
|
||||
public UnlocalizedNumberFormatter integerWidth(IntegerWidth style) {
|
||||
throw new AssertionError("See NumberFormatterImpl");
|
||||
}
|
||||
|
||||
public UnlocalizedNumberFormatter symbols(DecimalFormatSymbols symbols) {
|
||||
throw new AssertionError("See NumberFormatterImpl");
|
||||
}
|
||||
|
||||
public UnlocalizedNumberFormatter symbols(NumberingSystem ns) {
|
||||
throw new AssertionError("See NumberFormatterImpl");
|
||||
}
|
||||
|
||||
public UnlocalizedNumberFormatter unitWidth(FormatWidth style) {
|
||||
throw new AssertionError("See NumberFormatterImpl");
|
||||
}
|
||||
|
||||
public UnlocalizedNumberFormatter sign(SignDisplay style) {
|
||||
throw new AssertionError("See NumberFormatterImpl");
|
||||
}
|
||||
|
||||
public UnlocalizedNumberFormatter decimal(DecimalMarkDisplay style) {
|
||||
throw new AssertionError("See NumberFormatterImpl");
|
||||
}
|
||||
|
||||
public LocalizedNumberFormatter locale(Locale locale) {
|
||||
throw new AssertionError("See NumberFormatterImpl");
|
||||
}
|
||||
|
||||
public LocalizedNumberFormatter locale(ULocale locale) {
|
||||
throw new AssertionError("See NumberFormatterImpl");
|
||||
}
|
||||
|
||||
public String toSkeleton() {
|
||||
throw new AssertionError("See NumberFormatterImpl");
|
||||
}
|
||||
|
||||
// Prevent external subclassing with private constructor
|
||||
private UnlocalizedNumberFormatter() {}
|
||||
}
|
||||
|
||||
public static class LocalizedNumberFormatter extends UnlocalizedNumberFormatter {
|
||||
|
||||
@Override
|
||||
public UnlocalizedNumberFormatter notation(Notation notation) {
|
||||
throw new AssertionError("See NumberFormatterImpl");
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalizedNumberFormatter unit(MeasureUnit unit) {
|
||||
throw new AssertionError("See NumberFormatterImpl");
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalizedNumberFormatter rounding(IRounding rounding) {
|
||||
throw new AssertionError("See NumberFormatterImpl");
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalizedNumberFormatter grouping(IGrouping grouping) {
|
||||
throw new AssertionError("See NumberFormatterImpl");
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalizedNumberFormatter padding(Padding padding) {
|
||||
throw new AssertionError("See NumberFormatterImpl");
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalizedNumberFormatter integerWidth(IntegerWidth style) {
|
||||
throw new AssertionError("See NumberFormatterImpl");
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalizedNumberFormatter symbols(DecimalFormatSymbols symbols) {
|
||||
throw new AssertionError("See NumberFormatterImpl");
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalizedNumberFormatter symbols(NumberingSystem ns) {
|
||||
throw new AssertionError("See NumberFormatterImpl");
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalizedNumberFormatter unitWidth(FormatWidth style) {
|
||||
throw new AssertionError("See NumberFormatterImpl");
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalizedNumberFormatter sign(SignDisplay style) {
|
||||
throw new AssertionError("See NumberFormatterImpl");
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalizedNumberFormatter decimal(DecimalMarkDisplay style) {
|
||||
throw new AssertionError("See NumberFormatterImpl");
|
||||
}
|
||||
|
||||
public NumberFormatterResult format(long input) {
|
||||
throw new AssertionError("See NumberFormatterImpl");
|
||||
}
|
||||
|
||||
public NumberFormatterResult format(double input) {
|
||||
throw new AssertionError("See NumberFormatterImpl");
|
||||
}
|
||||
|
||||
public NumberFormatterResult format(Number input) {
|
||||
throw new AssertionError("See NumberFormatterImpl");
|
||||
}
|
||||
|
||||
public NumberFormatterResult format(Measure input) {
|
||||
throw new AssertionError("See NumberFormatterImpl");
|
||||
}
|
||||
|
||||
// Prevent external subclassing with private constructor
|
||||
private LocalizedNumberFormatter() {}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @deprecated This API is for ICU internal use only.
|
||||
*/
|
||||
@Deprecated
|
||||
public static class Internal extends LocalizedNumberFormatter {}
|
||||
}
|
||||
|
||||
public static UnlocalizedNumberFormatter fromSkeleton(String skeleton) {
|
||||
// FIXME
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public static UnlocalizedNumberFormatter with() {
|
||||
return NumberFormatterImpl.with();
|
||||
}
|
||||
|
||||
public static LocalizedNumberFormatter withLocale(Locale locale) {
|
||||
return NumberFormatterImpl.with().locale(locale);
|
||||
}
|
||||
|
||||
public static LocalizedNumberFormatter withLocale(ULocale locale) {
|
||||
return NumberFormatterImpl.with().locale(locale);
|
||||
}
|
||||
|
||||
public static class NumberFormatterResult {
|
||||
NumberStringBuilder nsb;
|
||||
FormatQuantityBCD fq;
|
||||
MicroProps micros;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @deprecated This API is ICU internal only.
|
||||
*/
|
||||
@Deprecated
|
||||
public NumberFormatterResult(NumberStringBuilder nsb, FormatQuantityBCD fq, MicroProps micros) {
|
||||
this.nsb = nsb;
|
||||
this.fq = fq;
|
||||
this.micros = micros;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return nsb.toString();
|
||||
}
|
||||
|
||||
public <A extends Appendable> A appendTo(A appendable) {
|
||||
try {
|
||||
appendable.append(nsb);
|
||||
} catch (IOException e) {
|
||||
// Throw as an unchecked exception to avoid users needing try/catch
|
||||
throw new ICUUncheckedIOException(e);
|
||||
}
|
||||
return appendable;
|
||||
}
|
||||
|
||||
public AttributedCharacterIterator toAttributedCharacterIterator() {
|
||||
return nsb.getIterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @deprecated This API a technology preview. It is not stable and may change or go away in an
|
||||
* upcoming release.
|
||||
*/
|
||||
@Deprecated
|
||||
public void populateFieldPosition(FieldPosition fieldPosition, int offset) {
|
||||
nsb.populateFieldPosition(fieldPosition, offset);
|
||||
fq.populateUFieldPosition(fieldPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @deprecated This API a technology preview. It is not stable and may change or go away in an
|
||||
* upcoming release.
|
||||
*/
|
||||
@Deprecated
|
||||
public String getPrefix() {
|
||||
return micros.modOuter.getPrefix()
|
||||
+ micros.modMiddle.getPrefix()
|
||||
+ micros.modInner.getPrefix();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @deprecated This API a technology preview. It is not stable and may change or go away in an
|
||||
* upcoming release.
|
||||
*/
|
||||
@Deprecated
|
||||
public String getSuffix() {
|
||||
return micros.modInner.getSuffix()
|
||||
+ micros.modMiddle.getSuffix()
|
||||
+ micros.modOuter.getSuffix();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @deprecated This API a technology preview. It is not stable and may change or go away in an
|
||||
* upcoming release.
|
||||
*/
|
||||
@Deprecated
|
||||
public IFixedDecimal getFixedDecimal() {
|
||||
return fq;
|
||||
}
|
||||
|
||||
public BigDecimal toBigDecimal() {
|
||||
return fq.toBigDecimal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
// NumberStringBuilder and BigDecimal are mutable, so we can't call
|
||||
// #equals() or #hashCode() on them directly.
|
||||
return Arrays.hashCode(nsb.toCharArray())
|
||||
^ Arrays.hashCode(nsb.toFieldArray())
|
||||
^ fq.toBigDecimal().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (this == other) return true;
|
||||
if (other == null) return false;
|
||||
if (!(other instanceof NumberFormatterResult)) return false;
|
||||
// NumberStringBuilder and BigDecimal are mutable, so we can't call
|
||||
// #equals() or #hashCode() on them directly.
|
||||
NumberFormatterResult _other = (NumberFormatterResult) other;
|
||||
return Arrays.equals(nsb.toCharArray(), _other.nsb.toCharArray())
|
||||
^ Arrays.equals(nsb.toFieldArray(), _other.nsb.toFieldArray())
|
||||
^ fq.toBigDecimal().equals(_other.fq.toBigDecimal());
|
||||
}
|
||||
}
|
||||
|
||||
public static class Notation {
|
||||
|
||||
// FIXME: Support engineering intervals other than 3?
|
||||
public static final NotationScientific SCIENTIFIC = new NotationScientificImpl(1);
|
||||
public static final NotationScientific ENGINEERING = new NotationScientificImpl(3);
|
||||
public static final NotationCompact COMPACT_SHORT = new NotationCompactImpl(CompactStyle.SHORT);
|
||||
public static final NotationCompact COMPACT_LONG = new NotationCompactImpl(CompactStyle.LONG);
|
||||
public static final NotationSimple SIMPLE = new NotationSimple();
|
||||
|
||||
// Prevent subclassing
|
||||
private Notation() {}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class NotationScientific extends Notation {
|
||||
|
||||
public NotationScientific withMinExponentDigits(int minExponentDigits) {
|
||||
// Overridden in NotationImpl
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
public NotationScientific withExponentSignDisplay(SignDisplay exponentSignDisplay) {
|
||||
// Overridden in NotationImpl
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
// Prevent subclassing
|
||||
private NotationScientific() {}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @deprecated This API is for ICU internal use only.
|
||||
*/
|
||||
@Deprecated
|
||||
public static class Internal extends NotationScientific {}
|
||||
}
|
||||
|
||||
public static class NotationCompact extends Notation {
|
||||
|
||||
// Prevent subclassing
|
||||
private NotationCompact() {}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @deprecated This API is for ICU internal use only.
|
||||
*/
|
||||
@Deprecated
|
||||
public static class Internal extends NotationCompact {}
|
||||
}
|
||||
|
||||
public static class NotationSimple extends Notation {
|
||||
// Prevent subclassing
|
||||
private NotationSimple() {}
|
||||
}
|
||||
|
||||
public static class Rounding implements IRounding {
|
||||
|
||||
protected static final int MAX_VALUE = 100;
|
||||
|
||||
public static final Rounding NONE = new RoundingImplInfinity();
|
||||
public static final Rounding INTEGER = new RoundingImplFraction();
|
||||
|
||||
public static FractionRounding fixedFraction(int minMaxFrac) {
|
||||
if (minMaxFrac >= 0 && minMaxFrac <= MAX_VALUE) {
|
||||
return RoundingImplFraction.getInstance(minMaxFrac, minMaxFrac);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Fraction length must be between 0 and " + MAX_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
public static FractionRounding minFraction(int minFrac) {
|
||||
if (minFrac >= 0 && minFrac < MAX_VALUE) {
|
||||
return RoundingImplFraction.getInstance(minFrac, Integer.MAX_VALUE);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Fraction length must be between 0 and " + MAX_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
public static FractionRounding maxFraction(int maxFrac) {
|
||||
if (maxFrac >= 0 && maxFrac < MAX_VALUE) {
|
||||
return RoundingImplFraction.getInstance(0, maxFrac);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Fraction length must be between 0 and " + MAX_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
public static FractionRounding minMaxFraction(int minFrac, int maxFrac) {
|
||||
if (minFrac >= 0 && maxFrac <= MAX_VALUE && minFrac <= maxFrac) {
|
||||
return RoundingImplFraction.getInstance(minFrac, maxFrac);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Fraction length must be between 0 and " + MAX_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
public static Rounding fixedFigures(int minMaxSig) {
|
||||
if (minMaxSig > 0 && minMaxSig <= MAX_VALUE) {
|
||||
return RoundingImplSignificant.getInstance(minMaxSig, minMaxSig);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Significant digits must be between 0 and " + MAX_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
public static Rounding minFigures(int minSig) {
|
||||
if (minSig > 0 && minSig <= MAX_VALUE) {
|
||||
return RoundingImplSignificant.getInstance(minSig, Integer.MAX_VALUE);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Significant digits must be between 0 and " + MAX_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
public static Rounding maxFigures(int maxSig) {
|
||||
if (maxSig > 0 && maxSig <= MAX_VALUE) {
|
||||
return RoundingImplSignificant.getInstance(0, maxSig);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Significant digits must be between 0 and " + MAX_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
public static Rounding minMaxFigures(int minSig, int maxSig) {
|
||||
if (minSig > 0 && maxSig <= MAX_VALUE && minSig <= maxSig) {
|
||||
return RoundingImplSignificant.getInstance(minSig, maxSig);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Significant digits must be between 0 and " + MAX_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
public static Rounding increment(BigDecimal roundingIncrement) {
|
||||
if (roundingIncrement == null) {
|
||||
throw new IllegalArgumentException("Rounding increment must be non-null");
|
||||
} else if (roundingIncrement.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw new IllegalArgumentException("Rounding increment must be positive");
|
||||
} else {
|
||||
return RoundingImplIncrement.getInstance(roundingIncrement);
|
||||
}
|
||||
}
|
||||
|
||||
public static CurrencyRounding currency(CurrencyUsage currencyUsage) {
|
||||
if (currencyUsage != CurrencyUsage.STANDARD && currencyUsage != CurrencyUsage.CASH) {
|
||||
throw new IllegalArgumentException("Unknown CurrencyUsage: " + currencyUsage);
|
||||
} else {
|
||||
return RoundingImplCurrency.getInstance(currencyUsage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link java.math.RoundingMode} to use when picking the direction to round (up or
|
||||
* down).
|
||||
*
|
||||
* <p>Common values include {@link RoundingMode#HALF_EVEN}, {@link RoundingMode#HALF_UP}, and
|
||||
* {@link RoundingMode#CEILING}. The default is HALF_EVEN.
|
||||
*
|
||||
* @param roundingMode The RoundingMode to use.
|
||||
* @return An immutable object for chaining.
|
||||
*/
|
||||
public Rounding withMode(RoundingMode roundingMode) {
|
||||
// Overridden in RoundingImpl
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a MathContext directly instead of RoundingMode.
|
||||
*
|
||||
* @internal
|
||||
* @deprecated This API is ICU internal only.
|
||||
*/
|
||||
@Deprecated
|
||||
public Rounding withMode(MathContext mathContext) {
|
||||
// Overridden in RoundingImpl
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal round(BigDecimal input) {
|
||||
// Overridden in RoundingImpl
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
// Prevent subclassing
|
||||
private Rounding() {}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @deprecated This API is for ICU internal use only.
|
||||
*/
|
||||
@Deprecated
|
||||
public static class Internal extends Rounding {}
|
||||
}
|
||||
|
||||
/**
|
||||
* A rounding strategy based on a minimum and/or maximum number of fraction digits. Allows for a
|
||||
* minimum or maximum number of significant digits to be specified.
|
||||
*/
|
||||
public static class FractionRounding extends Rounding {
|
||||
/**
|
||||
* Ensures that no less than this number of significant figures are retained when rounding
|
||||
* according to fraction rules.
|
||||
*
|
||||
* <p>For example, with integer rounding, the number 3.141 becomes "3". However, with minimum
|
||||
* figures set to 2, 3.141 becomes "3.1" instead.
|
||||
*
|
||||
* <p>This setting does not affect the number of trailing zeros. For example, 3.01 would print
|
||||
* as "3", not "3.0".
|
||||
*
|
||||
* @param minFigures The number of significant figures to guarantee.
|
||||
* @return An immutable object for chaining.
|
||||
*/
|
||||
public Rounding withMinFigures(int minFigures) {
|
||||
// Overridden in RoundingImpl
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that no more than this number of significant figures are retained when rounding
|
||||
* according to fraction rules.
|
||||
*
|
||||
* <p>For example, with integer rounding, the number 123.4 becomes "123". However, with maximum
|
||||
* figures set to 2, 123.4 becomes "120" instead.
|
||||
*
|
||||
* <p>This setting does not affect the number of trailing zeros. For example, with fixed
|
||||
* fraction of 2, 123.4 would become "120.00".
|
||||
*
|
||||
* @param maxFigures
|
||||
* @return An immutable object for chaining.
|
||||
*/
|
||||
public Rounding withMaxFigures(int maxFigures) {
|
||||
// Overridden in RoundingImpl
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
// Prevent subclassing
|
||||
private FractionRounding() {}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @deprecated This API is for ICU internal use only.
|
||||
*/
|
||||
@Deprecated
|
||||
public static class Internal extends FractionRounding {}
|
||||
}
|
||||
|
||||
/** A rounding strategy parameterized by a currency. */
|
||||
public static class CurrencyRounding extends Rounding {
|
||||
/**
|
||||
* Associates a {@link com.ibm.icu.util.Currency} with this rounding strategy. Only applies to
|
||||
* rounding strategies returned from {@link #currency(CurrencyUsage)}.
|
||||
*
|
||||
* <p><strong>Calling this method is <em>not required</em></strong>, because the currency
|
||||
* specified in {@link NumberFormatter#unit(MeasureUnit)} or via a {@link CurrencyAmount} passed
|
||||
* into {@link LocalizedNumberFormatter#format(Measure)} is automatically applied to currency
|
||||
* rounding strategies. However, this method enables you to override that automatic association.
|
||||
*
|
||||
* <p>This method also enables numbers to be formatted using currency rounding rules without
|
||||
* explicitly using a currency format.
|
||||
*
|
||||
* @param currency The currency to associate with this rounding strategy.
|
||||
* @return An immutable object for chaining.
|
||||
*/
|
||||
public Rounding withCurrency(Currency currency) {
|
||||
// Overridden in RoundingImpl
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
// Prevent subclassing
|
||||
private CurrencyRounding() {}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @deprecated This API is for ICU internal use only.
|
||||
*/
|
||||
@Deprecated
|
||||
public static class Internal extends CurrencyRounding {}
|
||||
}
|
||||
|
||||
public static class Grouping implements IGrouping {
|
||||
|
||||
public static final Grouping DEFAULT = new GroupingImpl(GroupingImpl.TYPE_PLACEHOLDER);
|
||||
public static final Grouping DEFAULT_MIN_2_DIGITS = new GroupingImpl(GroupingImpl.TYPE_MIN2);
|
||||
public static final Grouping NONE = new GroupingImpl(GroupingImpl.TYPE_NONE);
|
||||
|
||||
@Override
|
||||
public boolean groupAtPosition(int position, BigDecimal input) {
|
||||
throw new UnsupportedOperationException(
|
||||
"This grouping strategy cannot be used outside of number formatting.");
|
||||
}
|
||||
|
||||
// Prevent subclassing
|
||||
private Grouping() {}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @deprecated This API is for ICU internal use only.
|
||||
*/
|
||||
@Deprecated
|
||||
public static class Internal extends Grouping {}
|
||||
}
|
||||
|
||||
public static class Padding {
|
||||
|
||||
public static final Padding NONE = new PaddingImpl();
|
||||
|
||||
public static Padding codePoints(int cp, int targetWidth, PadPosition position) {
|
||||
String paddingString = String.valueOf(Character.toChars(cp));
|
||||
return PaddingImpl.getInstance(paddingString, targetWidth, position);
|
||||
}
|
||||
|
||||
// Prevent subclassing
|
||||
private Padding() {}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @deprecated This API is for ICU internal use only.
|
||||
*/
|
||||
@Deprecated
|
||||
public static class Internal extends Padding {}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class IntegerWidth {
|
||||
|
||||
public static final IntegerWidth DEFAULT = new IntegerWidthImpl();
|
||||
|
||||
public static IntegerWidth zeroFillTo(int minInt) {
|
||||
return new IntegerWidthImpl(minInt, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
public IntegerWidth truncateAt(int maxInt) {
|
||||
// Implemented in IntegerWidthImpl
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
// Prevent subclassing
|
||||
private IntegerWidth() {}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @deprecated This API is for ICU internal use only.
|
||||
*/
|
||||
@Deprecated
|
||||
public static class Internal extends IntegerWidth {}
|
||||
}
|
||||
}
|
63
icu4j/main/classes/core/src/newapi/demo.java
Normal file
63
icu4j/main/classes/core/src/newapi/demo.java
Normal file
@ -0,0 +1,63 @@
|
||||
// © 2017 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package newapi;
|
||||
|
||||
import java.math.RoundingMode;
|
||||
|
||||
import com.ibm.icu.text.DecimalFormatSymbols;
|
||||
import com.ibm.icu.text.MeasureFormat.FormatWidth;
|
||||
import com.ibm.icu.text.NumberingSystem;
|
||||
import com.ibm.icu.util.Currency;
|
||||
import com.ibm.icu.util.Currency.CurrencyUsage;
|
||||
import com.ibm.icu.util.Dimensionless;
|
||||
import com.ibm.icu.util.MeasureUnit;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
|
||||
import newapi.NumberFormatter.DecimalMarkDisplay;
|
||||
import newapi.NumberFormatter.Grouping;
|
||||
import newapi.NumberFormatter.Notation;
|
||||
import newapi.NumberFormatter.Rounding;
|
||||
import newapi.NumberFormatter.SignDisplay;
|
||||
import newapi.NumberFormatter.UnlocalizedNumberFormatter;
|
||||
|
||||
public class demo {
|
||||
public static void main(String[] args) {
|
||||
System.out.println(NumberingSystem.LATIN.getDescription());
|
||||
UnlocalizedNumberFormatter formatter =
|
||||
NumberFormatter.with()
|
||||
.notation(Notation.COMPACT_SHORT)
|
||||
.notation(Notation.SCIENTIFIC.withExponentSignDisplay(SignDisplay.ALWAYS_SHOWN))
|
||||
.notation(Notation.ENGINEERING.withMinExponentDigits(2))
|
||||
.notation(Notation.SIMPLE)
|
||||
.unit(Currency.getInstance("GBP"))
|
||||
.unit(Dimensionless.PERCENT)
|
||||
.unit(MeasureUnit.CUBIC_METER)
|
||||
.unitWidth(FormatWidth.SHORT)
|
||||
// .rounding(Rounding.fixedSignificantDigits(3))
|
||||
// .rounding(
|
||||
// (BigDecimal input) -> {
|
||||
// return input.divide(new BigDecimal("0.02"), 0).multiply(new BigDecimal("0.02"));
|
||||
// })
|
||||
.rounding(Rounding.fixedFraction(2).withMode(RoundingMode.HALF_UP))
|
||||
.rounding(Rounding.INTEGER.withMode(RoundingMode.CEILING))
|
||||
.rounding(Rounding.currency(CurrencyUsage.STANDARD))
|
||||
// .grouping(
|
||||
// (int position, BigDecimal number) -> {
|
||||
// return (position % 3) == 0;
|
||||
// })
|
||||
.grouping(Grouping.DEFAULT)
|
||||
.grouping(Grouping.NONE)
|
||||
.grouping(Grouping.DEFAULT_MIN_2_DIGITS)
|
||||
// .padding(Padding.codePoints(' ', 8, PadPosition.AFTER_PREFIX))
|
||||
.sign(SignDisplay.ALWAYS_SHOWN)
|
||||
.decimal(DecimalMarkDisplay.ALWAYS_SHOWN)
|
||||
.symbols(DecimalFormatSymbols.getInstance(new ULocale("fr@digits=ascii")))
|
||||
.symbols(NumberingSystem.getInstanceByName("arab"))
|
||||
.symbols(NumberingSystem.LATIN);
|
||||
System.out.println(formatter.toSkeleton());
|
||||
System.out.println(formatter.locale(ULocale.ENGLISH).format(0.98381).toString());
|
||||
// .locale(Locale.ENGLISH)
|
||||
// .format(123.45)
|
||||
// .toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
// © 2017 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package newapi.impl;
|
||||
|
||||
public interface AffixPatternProvider {
|
||||
public static final class Flags {
|
||||
public static final int PLURAL_MASK = 0xff;
|
||||
public static final int PREFIX = 0x100;
|
||||
public static final int NEGATIVE_SUBPATTERN = 0x200;
|
||||
public static final int PADDING = 0x400;
|
||||
}
|
||||
|
||||
public char charAt(int flags, int i);
|
||||
|
||||
public int length(int flags);
|
||||
|
||||
public boolean hasCurrencySign();
|
||||
|
||||
public boolean positiveHasPlusSign();
|
||||
|
||||
public boolean hasNegativeSubpattern();
|
||||
|
||||
public boolean negativeHasMinusSign();
|
||||
|
||||
public boolean containsSymbolType(int type);
|
||||
}
|
276
icu4j/main/classes/core/src/newapi/impl/CompactData.java
Normal file
276
icu4j/main/classes/core/src/newapi/impl/CompactData.java
Normal file
@ -0,0 +1,276 @@
|
||||
// © 2017 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package newapi.impl;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.Set;
|
||||
|
||||
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.text.CompactDecimalFormat.CompactStyle;
|
||||
import com.ibm.icu.text.CompactDecimalFormat.CompactType;
|
||||
import com.ibm.icu.text.NumberingSystem;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
import com.ibm.icu.util.UResourceBundle;
|
||||
|
||||
class CompactData implements RoundingImpl.MultiplierProducer {
|
||||
|
||||
public static CompactData getInstance(
|
||||
ULocale locale, CompactType compactType, CompactStyle compactStyle) {
|
||||
// TODO: Add a data cache? It would be keyed by locale, compact type, and compact style.
|
||||
CompactData data = new CompactData();
|
||||
CompactDataSink sink = new CompactDataSink(data, compactType, compactStyle);
|
||||
String nsName = NumberingSystem.getInstance(locale).getName();
|
||||
ICUResourceBundle rb =
|
||||
(ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, locale);
|
||||
CompactData.internalPopulateData(nsName, rb, sink, data);
|
||||
if (data.isEmpty() && compactStyle == CompactStyle.LONG) {
|
||||
// No long data is available; load short data instead
|
||||
sink.compactStyle = CompactStyle.SHORT;
|
||||
CompactData.internalPopulateData(nsName, rb, sink, data);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
public static CompactData getInstance(
|
||||
Map<String, Map<String, String>> powersToPluralsToPatterns) {
|
||||
CompactData data = new CompactData();
|
||||
for (Map.Entry<String, Map<String, String>> magnitudeEntry :
|
||||
powersToPluralsToPatterns.entrySet()) {
|
||||
byte magnitude = (byte) (magnitudeEntry.getKey().length() - 1);
|
||||
for (Map.Entry<String, String> pluralEntry : magnitudeEntry.getValue().entrySet()) {
|
||||
StandardPlural plural = StandardPlural.fromString(pluralEntry.getKey().toString());
|
||||
String patternString = pluralEntry.getValue().toString();
|
||||
data.setPattern(patternString, magnitude, plural);
|
||||
int numZeros = countZeros(patternString);
|
||||
if (numZeros > 0) { // numZeros==0 in certain cases, like Somali "Kun"
|
||||
data.setMultiplier(magnitude, (byte) (numZeros - magnitude - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
private static void internalPopulateData(
|
||||
String nsName, ICUResourceBundle rb, CompactDataSink sink, CompactData data) {
|
||||
try {
|
||||
rb.getAllItemsWithFallback("NumberElements/" + nsName, sink);
|
||||
} catch (MissingResourceException e) {
|
||||
// Fall back to latn
|
||||
}
|
||||
if (data.isEmpty() && !nsName.equals("latn")) {
|
||||
rb.getAllItemsWithFallback("NumberElements/latn", sink);
|
||||
}
|
||||
if (sink.exception != null) {
|
||||
throw sink.exception;
|
||||
}
|
||||
}
|
||||
|
||||
// A dummy object used when a "0" compact decimal entry is encountered. This is necessary
|
||||
// in order to prevent falling back to root. Object equality ("==") is intended.
|
||||
private static final String USE_FALLBACK = "<USE FALLBACK>";
|
||||
|
||||
private final String[] patterns;
|
||||
private final byte[] multipliers;
|
||||
private boolean isEmpty;
|
||||
private int largestMagnitude;
|
||||
|
||||
private static final int MAX_DIGITS = 15;
|
||||
|
||||
private CompactData() {
|
||||
patterns = new String[(CompactData.MAX_DIGITS + 1) * StandardPlural.COUNT];
|
||||
multipliers = new byte[CompactData.MAX_DIGITS + 1];
|
||||
isEmpty = true;
|
||||
largestMagnitude = 0;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return isEmpty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMultiplier(int magnitude) {
|
||||
if (magnitude < 0) {
|
||||
return 0;
|
||||
}
|
||||
if (magnitude > largestMagnitude) {
|
||||
magnitude = largestMagnitude;
|
||||
}
|
||||
return multipliers[magnitude];
|
||||
}
|
||||
|
||||
/** Returns the multiplier from the array directly without bounds checking. */
|
||||
public int getMultiplierDirect(int magnitude) {
|
||||
return multipliers[magnitude];
|
||||
}
|
||||
|
||||
private void setMultiplier(int magnitude, byte multiplier) {
|
||||
if (multipliers[magnitude] != 0) {
|
||||
assert multipliers[magnitude] == multiplier;
|
||||
return;
|
||||
}
|
||||
multipliers[magnitude] = multiplier;
|
||||
isEmpty = false;
|
||||
if (magnitude > largestMagnitude) largestMagnitude = magnitude;
|
||||
}
|
||||
|
||||
public String getPattern(int magnitude, StandardPlural plural) {
|
||||
if (magnitude < 0) {
|
||||
return null;
|
||||
}
|
||||
if (magnitude > largestMagnitude) {
|
||||
magnitude = largestMagnitude;
|
||||
}
|
||||
String patternString = patterns[getIndex(magnitude, plural)];
|
||||
if (patternString == null && plural != StandardPlural.OTHER) {
|
||||
// Fall back to "other" plural variant
|
||||
patternString = patterns[getIndex(magnitude, StandardPlural.OTHER)];
|
||||
}
|
||||
if (patternString == USE_FALLBACK) {
|
||||
// Return null if USE_FALLBACK is present
|
||||
patternString = null;
|
||||
}
|
||||
return patternString;
|
||||
}
|
||||
|
||||
public Set<String> getAllPatterns() {
|
||||
Set<String> result = new HashSet<String>();
|
||||
result.addAll(Arrays.asList(patterns));
|
||||
result.remove(USE_FALLBACK);
|
||||
result.remove(null);
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean has(int magnitude, StandardPlural plural) {
|
||||
// Return true if USE_FALLBACK is present
|
||||
return patterns[getIndex(magnitude, plural)] != null;
|
||||
}
|
||||
|
||||
private void setPattern(String patternString, int magnitude, StandardPlural plural) {
|
||||
patterns[getIndex(magnitude, plural)] = patternString;
|
||||
isEmpty = false;
|
||||
if (magnitude > largestMagnitude) largestMagnitude = magnitude;
|
||||
}
|
||||
|
||||
private void setNoFallback(int magnitude, StandardPlural plural) {
|
||||
setPattern(USE_FALLBACK, magnitude, plural);
|
||||
}
|
||||
|
||||
private static final int getIndex(int magnitude, StandardPlural plural) {
|
||||
return magnitude * StandardPlural.COUNT + plural.ordinal();
|
||||
}
|
||||
|
||||
private static final class CompactDataSink extends UResource.Sink {
|
||||
|
||||
CompactData data;
|
||||
CompactStyle compactStyle;
|
||||
CompactType compactType;
|
||||
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 CompactDataSink(CompactData data, CompactType compactType, CompactStyle compactStyle) {
|
||||
this.data = data;
|
||||
this.compactType = compactType;
|
||||
this.compactStyle = compactStyle;
|
||||
}
|
||||
|
||||
@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) {
|
||||
|
||||
// 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);
|
||||
byte multiplier = (byte) data.getMultiplierDirect(magnitude);
|
||||
|
||||
// Silently ignore divisors that are too big.
|
||||
if (magnitude >= CompactData.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;
|
||||
}
|
||||
|
||||
// Save the pattern string. We will parse it lazily.
|
||||
data.setPattern(patternString, magnitude, plural);
|
||||
|
||||
// If necessary, compute the multiplier: the difference between the magnitude
|
||||
// and the number of zeros in the pattern.
|
||||
if (multiplier == 0) {
|
||||
int numZeros = countZeros(patternString);
|
||||
if (numZeros > 0) { // numZeros==0 in certain cases, like Somali "Kun"
|
||||
multiplier = (byte) (numZeros - magnitude - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.setMultiplier(magnitude, multiplier);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final int countZeros(String patternString) {
|
||||
// NOTE: This strategy for computing the number of zeros is a hack for efficiency.
|
||||
// It could break if there are any 0s that aren't part of the main pattern.
|
||||
int numZeros = 0;
|
||||
for (int i = 0; i < patternString.length(); i++) {
|
||||
if (patternString.charAt(i) == '0') {
|
||||
numZeros++;
|
||||
} else if (numZeros > 0) {
|
||||
break; // zeros should always be contiguous
|
||||
}
|
||||
}
|
||||
return numZeros;
|
||||
}
|
||||
}
|
114
icu4j/main/classes/core/src/newapi/impl/CompactImpl.java
Normal file
114
icu4j/main/classes/core/src/newapi/impl/CompactImpl.java
Normal file
@ -0,0 +1,114 @@
|
||||
// © 2017 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package newapi.impl;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.ibm.icu.impl.StandardPlural;
|
||||
import com.ibm.icu.impl.number.FormatQuantity;
|
||||
import com.ibm.icu.impl.number.LdmlPatternInfo;
|
||||
import com.ibm.icu.impl.number.LdmlPatternInfo.PatternParseResult;
|
||||
import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
|
||||
import com.ibm.icu.text.CompactDecimalFormat.CompactType;
|
||||
import com.ibm.icu.text.PluralRules;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
|
||||
import newapi.impl.MurkyModifier.ImmutableMurkyModifier;
|
||||
import newapi.impl.RoundingImpl.RoundingImplDummy;
|
||||
|
||||
public class CompactImpl implements QuantityChain {
|
||||
|
||||
final PluralRules rules;
|
||||
final CompactData data;
|
||||
/* final */ Map<String, CompactModInfo> precomputedMods;
|
||||
/* final */ QuantityChain parent;
|
||||
|
||||
public static CompactImpl getInstance(
|
||||
ULocale dataLocale, CompactType compactType, CompactStyle compactStyle, PluralRules rules) {
|
||||
CompactData data = CompactData.getInstance(dataLocale, compactType, compactStyle);
|
||||
return new CompactImpl(data, rules);
|
||||
}
|
||||
|
||||
public static CompactImpl getInstance(
|
||||
Map<String, Map<String, String>> compactCustomData, PluralRules rules) {
|
||||
CompactData data = CompactData.getInstance(compactCustomData);
|
||||
return new CompactImpl(data, rules);
|
||||
}
|
||||
|
||||
private CompactImpl(CompactData data, PluralRules rules) {
|
||||
this.data = data;
|
||||
this.rules = rules;
|
||||
}
|
||||
|
||||
/** To be used by the building code path */
|
||||
public void precomputeAllModifiers(MurkyModifier reference) {
|
||||
precomputedMods = new HashMap<String, CompactModInfo>();
|
||||
Set<String> allPatterns = data.getAllPatterns();
|
||||
for (String patternString : allPatterns) {
|
||||
CompactModInfo info = new CompactModInfo();
|
||||
PatternParseResult patternInfo = LdmlPatternInfo.parse(patternString);
|
||||
reference.setPatternInfo(patternInfo);
|
||||
info.mod = reference.createImmutable();
|
||||
info.numDigits = patternInfo.positive.totalIntegerDigits;
|
||||
precomputedMods.put(patternString, info);
|
||||
}
|
||||
}
|
||||
|
||||
private static class CompactModInfo {
|
||||
public ImmutableMurkyModifier mod;
|
||||
public int numDigits;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuantityChain chain(QuantityChain parent) {
|
||||
this.parent = parent;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MicroProps withQuantity(FormatQuantity input) {
|
||||
MicroProps micros = parent.withQuantity(input);
|
||||
assert micros.rounding != null;
|
||||
|
||||
// Treat zero as if it had magnitude 0
|
||||
int magnitude;
|
||||
if (input.isZero()) {
|
||||
magnitude = 0;
|
||||
micros.rounding.apply(input);
|
||||
} else {
|
||||
// TODO: Revisit chooseMultiplierAndApply
|
||||
int multiplier = micros.rounding.chooseMultiplierAndApply(input, data);
|
||||
magnitude = input.isZero() ? 0 : input.getMagnitude();
|
||||
magnitude -= multiplier;
|
||||
}
|
||||
|
||||
StandardPlural plural = input.getStandardPlural(rules);
|
||||
String patternString = data.getPattern(magnitude, plural);
|
||||
int numDigits = -1;
|
||||
if (patternString == null) {
|
||||
// Use the default (non-compact) modifier.
|
||||
// No need to take any action.
|
||||
} else if (precomputedMods != null) {
|
||||
// Build code path.
|
||||
CompactModInfo info = precomputedMods.get(patternString);
|
||||
info.mod.applyToMicros(micros, input);
|
||||
numDigits = info.numDigits;
|
||||
} else {
|
||||
// Non-build code path.
|
||||
// Overwrite the PatternInfo in the existing modMiddle
|
||||
assert micros.modMiddle instanceof MurkyModifier;
|
||||
PatternParseResult patternInfo = LdmlPatternInfo.parse(patternString);
|
||||
((MurkyModifier) micros.modMiddle).setPatternInfo(patternInfo);
|
||||
numDigits = patternInfo.positive.totalIntegerDigits;
|
||||
}
|
||||
|
||||
// FIXME: Deal with numDigits == 0 (Awaiting a test case)
|
||||
|
||||
// We already performed rounding. Do not perform it again.
|
||||
micros.rounding = RoundingImplDummy.INSTANCE;
|
||||
|
||||
return micros;
|
||||
}
|
||||
}
|
@ -0,0 +1,173 @@
|
||||
// © 2017 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package newapi.impl;
|
||||
|
||||
import com.ibm.icu.impl.number.NumberStringBuilder;
|
||||
import com.ibm.icu.impl.number.modifiers.ConstantMultiFieldModifier;
|
||||
import com.ibm.icu.text.DecimalFormatSymbols;
|
||||
import com.ibm.icu.text.NumberFormat;
|
||||
import com.ibm.icu.text.UnicodeSet;
|
||||
|
||||
/** Identical to {@link ConstantMultiFieldModifier}, but supports currency spacing. */
|
||||
public class CurrencySpacingEnabledModifier extends ConstantMultiFieldModifier {
|
||||
|
||||
// These are the default currency spacing UnicodeSets in CLDR.
|
||||
// Pre-compute them for performance.
|
||||
// TODO: Is there a way to write a unit test to make sure these hard-coded values
|
||||
// stay consistent with CLDR?
|
||||
private static final UnicodeSet UNISET_DIGIT = new UnicodeSet("[:digit:]").freeze();
|
||||
private static final UnicodeSet UNISET_NOTS = new UnicodeSet("[:^S:]").freeze();
|
||||
|
||||
// Constants for better readability. Types are for compiler checking.
|
||||
static final byte PREFIX = 0;
|
||||
static final byte SUFFIX = 1;
|
||||
static final short IN_CURRENCY = 0;
|
||||
static final short IN_NUMBER = 1;
|
||||
|
||||
private final UnicodeSet afterPrefixUnicodeSet;
|
||||
private final String afterPrefixInsert;
|
||||
private final UnicodeSet beforeSuffixUnicodeSet;
|
||||
private final String beforeSuffixInsert;
|
||||
|
||||
/** Build code path */
|
||||
public CurrencySpacingEnabledModifier(
|
||||
NumberStringBuilder prefix,
|
||||
NumberStringBuilder suffix,
|
||||
boolean strong,
|
||||
DecimalFormatSymbols symbols) {
|
||||
super(prefix, suffix, strong);
|
||||
|
||||
// Check for currency spacing. Do not build the UnicodeSets unless there is
|
||||
// a currency code point at a boundary.
|
||||
if (prefixFields.length > 0
|
||||
&& prefixFields[prefixFields.length - 1] == NumberFormat.Field.CURRENCY) {
|
||||
int prefixCp = Character.codePointBefore(prefixChars, prefixChars.length);
|
||||
UnicodeSet prefixUnicodeSet = getUnicodeSet(symbols, IN_CURRENCY, PREFIX);
|
||||
if (prefixUnicodeSet.contains(prefixCp)) {
|
||||
afterPrefixUnicodeSet = getUnicodeSet(symbols, IN_NUMBER, PREFIX);
|
||||
afterPrefixInsert = getInsertString(symbols, PREFIX);
|
||||
} else {
|
||||
afterPrefixUnicodeSet = null;
|
||||
afterPrefixInsert = null;
|
||||
}
|
||||
} else {
|
||||
afterPrefixUnicodeSet = null;
|
||||
afterPrefixInsert = null;
|
||||
}
|
||||
if (suffixFields.length > 0 && suffixFields[0] == NumberFormat.Field.CURRENCY) {
|
||||
int suffixCp = Character.codePointAt(suffixChars, 0);
|
||||
UnicodeSet suffixUnicodeSet = getUnicodeSet(symbols, IN_CURRENCY, SUFFIX);
|
||||
if (suffixUnicodeSet.contains(suffixCp)) {
|
||||
beforeSuffixUnicodeSet = getUnicodeSet(symbols, IN_NUMBER, SUFFIX);
|
||||
beforeSuffixInsert = getInsertString(symbols, SUFFIX);
|
||||
} else {
|
||||
beforeSuffixUnicodeSet = null;
|
||||
beforeSuffixInsert = null;
|
||||
}
|
||||
} else {
|
||||
beforeSuffixUnicodeSet = null;
|
||||
beforeSuffixInsert = null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Non-build code path */
|
||||
public static int applyCurrencySpacing(
|
||||
NumberStringBuilder output,
|
||||
int prefixStart,
|
||||
int prefixLen,
|
||||
int suffixStart,
|
||||
int suffixLen,
|
||||
DecimalFormatSymbols symbols) {
|
||||
int length = 0;
|
||||
boolean hasPrefix = (prefixLen > 0);
|
||||
boolean hasSuffix = (suffixLen > 0);
|
||||
boolean hasNumber = (suffixStart - prefixStart - prefixLen > 0); // could be empty string
|
||||
if (hasPrefix && hasNumber) {
|
||||
length += applyCurrencySpacingAffix(output, prefixStart + prefixLen, PREFIX, symbols);
|
||||
}
|
||||
if (hasSuffix && hasNumber) {
|
||||
length += applyCurrencySpacingAffix(output, suffixStart + length, SUFFIX, symbols);
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
private static int applyCurrencySpacingAffix(
|
||||
NumberStringBuilder output, int index, byte affix, DecimalFormatSymbols symbols) {
|
||||
// NOTE: For prefix, output.fieldAt(index-1) gets the last field type in the prefix.
|
||||
// This works even if the last code point in the prefix is 2 code units because the
|
||||
// field value gets populated to both indices in the field array.
|
||||
NumberFormat.Field affixField =
|
||||
(affix == PREFIX) ? output.fieldAt(index - 1) : output.fieldAt(index);
|
||||
if (affixField != NumberFormat.Field.CURRENCY) {
|
||||
return 0;
|
||||
}
|
||||
int affixCp =
|
||||
(affix == PREFIX)
|
||||
? Character.codePointBefore(output, index)
|
||||
: Character.codePointAt(output, index);
|
||||
UnicodeSet affixUniset = getUnicodeSet(symbols, IN_CURRENCY, affix);
|
||||
if (!affixUniset.contains(affixCp)) {
|
||||
return 0;
|
||||
}
|
||||
int numberCp =
|
||||
(affix == PREFIX)
|
||||
? Character.codePointAt(output, index)
|
||||
: Character.codePointBefore(output, index);
|
||||
UnicodeSet numberUniset = getUnicodeSet(symbols, IN_NUMBER, affix);
|
||||
if (!numberUniset.contains(numberCp)) {
|
||||
return 0;
|
||||
}
|
||||
String spacingString = getInsertString(symbols, affix);
|
||||
|
||||
// NOTE: This next line *inserts* the spacing string, triggering an arraycopy.
|
||||
// It would be more efficient if this could be done before affixes were attached,
|
||||
// so that it could be prepended/appended instead of inserted.
|
||||
// However, the build code path is more efficient, and this is the most natural
|
||||
// place to put currency spacing in the non-build code path.
|
||||
// TODO: Should we use the CURRENCY field here?
|
||||
return output.insert(index, spacingString, null);
|
||||
}
|
||||
|
||||
private static UnicodeSet getUnicodeSet(DecimalFormatSymbols symbols, short position, byte affix) {
|
||||
String pattern =
|
||||
symbols.getPatternForCurrencySpacing(
|
||||
position == IN_CURRENCY
|
||||
? DecimalFormatSymbols.CURRENCY_SPC_CURRENCY_MATCH
|
||||
: DecimalFormatSymbols.CURRENCY_SPC_SURROUNDING_MATCH,
|
||||
affix == SUFFIX);
|
||||
if (pattern.equals("[:digit:]")) {
|
||||
return UNISET_DIGIT;
|
||||
} else if (pattern.equals("[:^S:]")) {
|
||||
return UNISET_NOTS;
|
||||
} else {
|
||||
return new UnicodeSet(pattern);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getInsertString(DecimalFormatSymbols symbols, byte affix) {
|
||||
return symbols.getPatternForCurrencySpacing(
|
||||
DecimalFormatSymbols.CURRENCY_SPC_INSERT, affix == SUFFIX);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int apply(NumberStringBuilder output, int leftIndex, int rightIndex) {
|
||||
// Currency spacing logic
|
||||
int length = 0;
|
||||
if (rightIndex - leftIndex > 0
|
||||
&& afterPrefixUnicodeSet != null
|
||||
&& afterPrefixUnicodeSet.contains(Character.codePointAt(output, leftIndex))) {
|
||||
// TODO: Should we use the CURRENCY field here?
|
||||
length += output.insert(leftIndex, afterPrefixInsert, null);
|
||||
}
|
||||
if (rightIndex - leftIndex > 0
|
||||
&& beforeSuffixUnicodeSet != null
|
||||
&& beforeSuffixUnicodeSet.contains(Character.codePointBefore(output, rightIndex))) {
|
||||
// TODO: Should we use the CURRENCY field here?
|
||||
length += output.insert(rightIndex + length, beforeSuffixInsert, null);
|
||||
}
|
||||
|
||||
// Call super for the remaining logic
|
||||
length += super.apply(output, leftIndex, rightIndex + length);
|
||||
return length;
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
// © 2017 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package newapi.impl;
|
||||
|
||||
import com.ibm.icu.text.DecimalFormatSymbols;
|
||||
import com.ibm.icu.util.Currency;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
|
||||
public class CustomSymbolCurrency extends Currency {
|
||||
private String symbol1;
|
||||
private String symbol2;
|
||||
|
||||
public static Currency resolve(Currency currency, ULocale locale, DecimalFormatSymbols symbols) {
|
||||
if (currency == null) {
|
||||
currency = symbols.getCurrency();
|
||||
}
|
||||
String currency1Sym = symbols.getCurrencySymbol();
|
||||
String currency2Sym = symbols.getInternationalCurrencySymbol();
|
||||
if (currency == null) {
|
||||
return new CustomSymbolCurrency("XXX", currency1Sym, currency2Sym);
|
||||
}
|
||||
if (!currency.equals(symbols.getCurrency())) {
|
||||
return currency;
|
||||
}
|
||||
String currency1 = currency.getName(symbols.getULocale(), Currency.SYMBOL_NAME, null);
|
||||
String currency2 = currency.getCurrencyCode();
|
||||
if (!currency1.equals(currency1Sym) || !currency2.equals(currency2Sym)) {
|
||||
return new CustomSymbolCurrency(currency2, currency1Sym, currency2Sym);
|
||||
}
|
||||
return currency;
|
||||
}
|
||||
|
||||
public CustomSymbolCurrency(String isoCode, String currency1Sym, String currency2Sym) {
|
||||
super(isoCode);
|
||||
this.symbol1 = currency1Sym;
|
||||
this.symbol2 = currency2Sym;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(ULocale locale, int nameStyle, boolean[] isChoiceFormat) {
|
||||
if (nameStyle == SYMBOL_NAME) {
|
||||
return symbol1;
|
||||
}
|
||||
return super.getName(locale, nameStyle, isChoiceFormat);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(
|
||||
ULocale locale, int nameStyle, String pluralCount, boolean[] isChoiceFormat) {
|
||||
if (nameStyle == PLURAL_LONG_NAME && subType.equals("XXX")) {
|
||||
// Plural in absence of a currency should return the symbol
|
||||
return symbol1;
|
||||
}
|
||||
return super.getName(locale, nameStyle, pluralCount, isChoiceFormat);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCurrencyCode() {
|
||||
return symbol2;
|
||||
}
|
||||
}
|
67
icu4j/main/classes/core/src/newapi/impl/DataUtils.java
Normal file
67
icu4j/main/classes/core/src/newapi/impl/DataUtils.java
Normal file
@ -0,0 +1,67 @@
|
||||
// © 2017 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package newapi.impl;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.ibm.icu.impl.CurrencyData;
|
||||
import com.ibm.icu.impl.SimpleFormatterImpl;
|
||||
import com.ibm.icu.impl.StandardPlural;
|
||||
import com.ibm.icu.impl.number.Modifier;
|
||||
import com.ibm.icu.impl.number.modifiers.SimpleModifier;
|
||||
import com.ibm.icu.text.MeasureFormat.FormatWidth;
|
||||
import com.ibm.icu.text.NumberFormat.Field;
|
||||
import com.ibm.icu.util.Currency;
|
||||
import com.ibm.icu.util.MeasureUnit;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
|
||||
public class DataUtils {
|
||||
|
||||
public static Map<StandardPlural, Modifier> getCurrencyLongNameModifiers(
|
||||
ULocale loc, Currency currency) {
|
||||
Map<String, String> data = CurrencyData.provider.getInstance(loc, true).getUnitPatterns();
|
||||
Map<StandardPlural, Modifier> result =
|
||||
new EnumMap<StandardPlural, Modifier>(StandardPlural.class);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Map.Entry<String, String> e : data.entrySet()) {
|
||||
String pluralKeyword = e.getKey();
|
||||
StandardPlural plural = StandardPlural.fromString(e.getKey());
|
||||
String longName = currency.getName(loc, Currency.PLURAL_LONG_NAME, pluralKeyword, null);
|
||||
String simpleFormat = e.getValue(); // e.g., "{0} {1}"
|
||||
simpleFormat = simpleFormat.replace("{1}", longName);
|
||||
String compiled = SimpleFormatterImpl.compileToStringMinMaxArguments(simpleFormat, sb, 1, 1);
|
||||
Modifier mod = new SimpleModifier(compiled, Field.CURRENCY, false);
|
||||
result.put(plural, mod);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Map<StandardPlural, Modifier> getMeasureUnitModifiers(
|
||||
ULocale loc, MeasureUnit unit, FormatWidth width) {
|
||||
Map<StandardPlural, String> simpleFormats = MeasureData.getMeasureData(loc, unit, width);
|
||||
Map<StandardPlural, Modifier> result =
|
||||
new EnumMap<StandardPlural, Modifier>(StandardPlural.class);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (StandardPlural plural : StandardPlural.VALUES) {
|
||||
if (simpleFormats.get(plural) == null) {
|
||||
plural = StandardPlural.OTHER;
|
||||
}
|
||||
String simpleFormat = simpleFormats.get(plural);
|
||||
String compiled = SimpleFormatterImpl.compileToStringMinMaxArguments(simpleFormat, sb, 1, 1);
|
||||
Modifier mod = new SimpleModifier(compiled, Field.CURRENCY, false);
|
||||
result.put(plural, mod);
|
||||
}
|
||||
return result;
|
||||
// Map<StandardPlural, Modifier> result =
|
||||
// new EnumMap<StandardPlural, Modifier>(StandardPlural.class);
|
||||
// // TODO: Get the data directly instead of taking the detour through MeasureFormat.
|
||||
// MeasureFormat mf = MeasureFormat.getInstance(loc, width);
|
||||
// for (StandardPlural plural : StandardPlural.VALUES) {
|
||||
// String compiled = mf.getPluralFormatter(unit, width, plural.ordinal());
|
||||
// Modifier mod = new SimpleModifier(compiled, null, false);
|
||||
// result.put(plural, mod);
|
||||
// }
|
||||
// return result;
|
||||
}
|
||||
}
|
134
icu4j/main/classes/core/src/newapi/impl/GroupingImpl.java
Normal file
134
icu4j/main/classes/core/src/newapi/impl/GroupingImpl.java
Normal file
@ -0,0 +1,134 @@
|
||||
// © 2017 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package newapi.impl;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import com.ibm.icu.impl.number.FormatQuantity;
|
||||
import com.ibm.icu.impl.number.LdmlPatternInfo.PatternParseResult;
|
||||
|
||||
import newapi.NumberFormatter.Grouping;
|
||||
import newapi.NumberFormatter.IGrouping;
|
||||
|
||||
public class GroupingImpl extends Grouping.Internal {
|
||||
|
||||
// Conveniences for Java handling of shorts
|
||||
private static final short S2 = 2;
|
||||
private static final short S3 = 3;
|
||||
|
||||
// For the "placeholder constructor"
|
||||
public static final char TYPE_PLACEHOLDER = 0;
|
||||
public static final char TYPE_MIN2 = 1;
|
||||
public static final char TYPE_NONE = 2;
|
||||
|
||||
// Statically initialized objects (cannot be used statically by other ICU classes)
|
||||
static final GroupingImpl NONE = new GroupingImpl(TYPE_NONE);
|
||||
static final GroupingImpl GROUPING_3 = new GroupingImpl(S3, S3, false);
|
||||
static final GroupingImpl GROUPING_3_2 = new GroupingImpl(S3, S2, false);
|
||||
static final GroupingImpl GROUPING_3_MIN2 = new GroupingImpl(S3, S3, true);
|
||||
static final GroupingImpl GROUPING_3_2_MIN2 = new GroupingImpl(S3, S2, true);
|
||||
|
||||
static GroupingImpl getInstance(short grouping1, short grouping2, boolean min2) {
|
||||
if (grouping1 == -1) {
|
||||
return NONE;
|
||||
} else if (!min2 && grouping1 == 3 && grouping2 == 3) {
|
||||
return GROUPING_3;
|
||||
} else if (!min2 && grouping1 == 3 && grouping2 == 2) {
|
||||
return GROUPING_3_2;
|
||||
} else if (min2 && grouping1 == 3 && grouping2 == 3) {
|
||||
return GROUPING_3_MIN2;
|
||||
} else if (min2 && grouping1 == 3 && grouping2 == 2) {
|
||||
return GROUPING_3_2_MIN2;
|
||||
} else {
|
||||
return new GroupingImpl(grouping1, grouping2, min2);
|
||||
}
|
||||
}
|
||||
|
||||
public static GroupingImpl normalizeType(IGrouping grouping, PatternParseResult patternInfo) {
|
||||
assert grouping != null;
|
||||
if (grouping instanceof GroupingImpl) {
|
||||
return ((GroupingImpl) grouping).withLocaleData(patternInfo);
|
||||
} else {
|
||||
return new GroupingImpl(grouping);
|
||||
}
|
||||
}
|
||||
|
||||
final IGrouping lambda;
|
||||
final short grouping1; // -2 means "needs locale data"; -1 means "no grouping"
|
||||
final short grouping2;
|
||||
final boolean min2;
|
||||
|
||||
/** The "placeholder constructor". Pass in one of the GroupingImpl.TYPE_* variables. */
|
||||
public GroupingImpl(char type) {
|
||||
lambda = null;
|
||||
switch (type) {
|
||||
case TYPE_PLACEHOLDER:
|
||||
grouping1 = -2;
|
||||
grouping2 = -2;
|
||||
min2 = false;
|
||||
break;
|
||||
case TYPE_MIN2:
|
||||
grouping1 = -2;
|
||||
grouping2 = -2;
|
||||
min2 = true;
|
||||
break;
|
||||
case TYPE_NONE:
|
||||
grouping1 = -1;
|
||||
grouping2 = -1;
|
||||
min2 = false;
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
private GroupingImpl(short grouping1, short grouping2, boolean min2) {
|
||||
this.lambda = null;
|
||||
this.grouping1 = grouping1;
|
||||
this.grouping2 = grouping2;
|
||||
this.min2 = min2;
|
||||
}
|
||||
|
||||
private GroupingImpl(IGrouping lambda) {
|
||||
this.lambda = lambda;
|
||||
this.grouping1 = -3;
|
||||
this.grouping2 = -3;
|
||||
this.min2 = false;
|
||||
}
|
||||
|
||||
GroupingImpl withLocaleData(PatternParseResult patternInfo) {
|
||||
if (grouping1 != -2) {
|
||||
return this;
|
||||
}
|
||||
assert lambda == null;
|
||||
short grouping1 = (short) (patternInfo.positive.groupingSizes & 0xffff);
|
||||
short grouping2 = (short) ((patternInfo.positive.groupingSizes >>> 16) & 0xffff);
|
||||
short grouping3 = (short) ((patternInfo.positive.groupingSizes >>> 32) & 0xffff);
|
||||
if (grouping2 == -1) {
|
||||
grouping1 = -1;
|
||||
}
|
||||
if (grouping3 == -1) {
|
||||
grouping2 = grouping1;
|
||||
}
|
||||
return getInstance(grouping1, grouping2, min2);
|
||||
}
|
||||
|
||||
boolean groupAtPosition(int position, FormatQuantity value) {
|
||||
// Check for lambda function
|
||||
if (lambda != null) {
|
||||
// TODO: Cache the BigDecimal
|
||||
BigDecimal temp = value.toBigDecimal();
|
||||
return lambda.groupAtPosition(position, temp);
|
||||
}
|
||||
|
||||
assert grouping1 != -2;
|
||||
if (grouping1 == -1 || grouping1 == 0) {
|
||||
// Either -1 or 0 means "no grouping"
|
||||
return false;
|
||||
}
|
||||
position -= grouping1;
|
||||
return position >= 0
|
||||
&& (position % grouping2) == 0
|
||||
&& value.getUpperDisplayMagnitude() - grouping1 + 1 >= (min2 ? 2 : 1);
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
// © 2017 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package newapi.impl;
|
||||
|
||||
import newapi.NumberFormatter.IntegerWidth;
|
||||
|
||||
public final class IntegerWidthImpl extends IntegerWidth.Internal {
|
||||
public final int minInt;
|
||||
public final int maxInt;
|
||||
|
||||
public static final IntegerWidthImpl DEFAULT = new IntegerWidthImpl();
|
||||
|
||||
/** Default constructor */
|
||||
public IntegerWidthImpl() {
|
||||
this(1, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
public IntegerWidthImpl(int minInt, int maxInt) {
|
||||
this.minInt = minInt;
|
||||
this.maxInt = maxInt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IntegerWidthImpl truncateAt(int maxInt) {
|
||||
return new IntegerWidthImpl(minInt, maxInt);
|
||||
}
|
||||
}
|
105
icu4j/main/classes/core/src/newapi/impl/MacroProps.java
Normal file
105
icu4j/main/classes/core/src/newapi/impl/MacroProps.java
Normal file
@ -0,0 +1,105 @@
|
||||
// © 2017 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package newapi.impl;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.ibm.icu.text.MeasureFormat.FormatWidth;
|
||||
import com.ibm.icu.text.PluralRules;
|
||||
import com.ibm.icu.util.MeasureUnit;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
|
||||
import newapi.NumberFormatter.DecimalMarkDisplay;
|
||||
import newapi.NumberFormatter.IGrouping;
|
||||
import newapi.NumberFormatter.IRounding;
|
||||
import newapi.NumberFormatter.IntegerWidth;
|
||||
import newapi.NumberFormatter.Notation;
|
||||
import newapi.NumberFormatter.Padding;
|
||||
import newapi.NumberFormatter.SignDisplay;
|
||||
|
||||
public class MacroProps implements Cloneable {
|
||||
public Notation notation;
|
||||
public MeasureUnit unit;
|
||||
public IRounding rounding;
|
||||
public IGrouping grouping;
|
||||
public Padding padding;
|
||||
public IntegerWidth integerWidth;
|
||||
public Object symbols;
|
||||
public FormatWidth unitWidth;
|
||||
public SignDisplay sign;
|
||||
public DecimalMarkDisplay decimal;
|
||||
public AffixPatternProvider affixProvider; // not in API; for JDK compatibility mode only
|
||||
public MultiplierImpl multiplier; // not in API; for JDK compatibility mode only
|
||||
public PluralRules rules; // not in API; could be made public in the future
|
||||
public ULocale loc;
|
||||
|
||||
/**
|
||||
* Copies values from fallback into this instance if they are null in this instance.
|
||||
*
|
||||
* @param fallback The instance to copy from; not modified by this operation.
|
||||
*/
|
||||
public void fallback(MacroProps fallback) {
|
||||
if (notation == null) notation = fallback.notation;
|
||||
if (unit == null) unit = fallback.unit;
|
||||
if (rounding == null) rounding = fallback.rounding;
|
||||
if (grouping == null) grouping = fallback.grouping;
|
||||
if (padding == null) padding = fallback.padding;
|
||||
if (integerWidth == null) integerWidth = fallback.integerWidth;
|
||||
if (symbols == null) symbols = fallback.symbols;
|
||||
if (unitWidth == null) unitWidth = fallback.unitWidth;
|
||||
if (sign == null) sign = fallback.sign;
|
||||
if (decimal == null) decimal = fallback.decimal;
|
||||
if (affixProvider == null) affixProvider = fallback.affixProvider;
|
||||
if (multiplier == null) multiplier = fallback.multiplier;
|
||||
if (rules == null) rules = fallback.rules;
|
||||
if (loc == null) loc = fallback.loc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(
|
||||
notation,
|
||||
unit,
|
||||
rounding,
|
||||
grouping,
|
||||
padding,
|
||||
integerWidth,
|
||||
symbols,
|
||||
unitWidth,
|
||||
sign,
|
||||
decimal,
|
||||
affixProvider,
|
||||
multiplier,
|
||||
rules,
|
||||
loc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object _other) {
|
||||
MacroProps other = (MacroProps) _other;
|
||||
return Objects.equals(notation, other.notation)
|
||||
&& Objects.equals(unit, other.unit)
|
||||
&& Objects.equals(rounding, other.rounding)
|
||||
&& Objects.equals(grouping, other.grouping)
|
||||
&& Objects.equals(padding, other.padding)
|
||||
&& Objects.equals(integerWidth, other.integerWidth)
|
||||
&& Objects.equals(symbols, other.symbols)
|
||||
&& Objects.equals(unitWidth, other.unitWidth)
|
||||
&& Objects.equals(sign, other.sign)
|
||||
&& Objects.equals(decimal, other.decimal)
|
||||
&& Objects.equals(affixProvider, other.affixProvider)
|
||||
&& Objects.equals(multiplier, other.multiplier)
|
||||
&& Objects.equals(rules, other.rules)
|
||||
&& Objects.equals(loc, other.loc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object clone() {
|
||||
// TODO: Remove this method?
|
||||
try {
|
||||
return super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
}
|
64
icu4j/main/classes/core/src/newapi/impl/MeasureData.java
Normal file
64
icu4j/main/classes/core/src/newapi/impl/MeasureData.java
Normal file
@ -0,0 +1,64 @@
|
||||
// © 2017 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package newapi.impl;
|
||||
|
||||
import java.util.EnumMap;
|
||||
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.text.MeasureFormat.FormatWidth;
|
||||
import com.ibm.icu.util.MeasureUnit;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
import com.ibm.icu.util.UResourceBundle;
|
||||
|
||||
public class MeasureData {
|
||||
|
||||
private static final class ShanesMeasureUnitSink extends UResource.Sink {
|
||||
|
||||
Map<StandardPlural, String> output;
|
||||
|
||||
public ShanesMeasureUnitSink(Map<StandardPlural, String> output) {
|
||||
this.output = output;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
|
||||
UResource.Table pluralsTable = value.getTable();
|
||||
for (int i1 = 0; pluralsTable.getKeyAndValue(i1, key, value); ++i1) {
|
||||
if (key.contentEquals("dnam") || key.contentEquals("per")) {
|
||||
continue;
|
||||
}
|
||||
StandardPlural plural = StandardPlural.fromString(key);
|
||||
if (output.containsKey(plural)) {
|
||||
continue;
|
||||
}
|
||||
String formatString = value.getString();
|
||||
output.put(plural, formatString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Map<StandardPlural, String> getMeasureData(
|
||||
ULocale locale, MeasureUnit unit, FormatWidth width) {
|
||||
ICUResourceBundle resource =
|
||||
(ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_UNIT_BASE_NAME, locale);
|
||||
StringBuilder key = new StringBuilder();
|
||||
key.append("units");
|
||||
if (width == FormatWidth.NARROW) {
|
||||
key.append("Narrow");
|
||||
} else if (width == FormatWidth.SHORT) {
|
||||
key.append("Short");
|
||||
}
|
||||
key.append("/");
|
||||
key.append(unit.getType());
|
||||
key.append("/");
|
||||
key.append(unit.getSubtype());
|
||||
Map<StandardPlural, String> output = new EnumMap<StandardPlural, String>(StandardPlural.class);
|
||||
ShanesMeasureUnitSink sink = new ShanesMeasureUnitSink(output);
|
||||
resource.getAllItemsWithFallback(key.toString(), sink);
|
||||
return output;
|
||||
}
|
||||
}
|
58
icu4j/main/classes/core/src/newapi/impl/MicroProps.java
Normal file
58
icu4j/main/classes/core/src/newapi/impl/MicroProps.java
Normal file
@ -0,0 +1,58 @@
|
||||
// © 2017 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package newapi.impl;
|
||||
|
||||
import com.ibm.icu.impl.number.FormatQuantity;
|
||||
import com.ibm.icu.impl.number.Modifier;
|
||||
import com.ibm.icu.text.DecimalFormatSymbols;
|
||||
|
||||
import newapi.NumberFormatter.DecimalMarkDisplay;
|
||||
import newapi.NumberFormatter.SignDisplay;
|
||||
|
||||
public class MicroProps implements Cloneable, QuantityChain {
|
||||
// Populated globally:
|
||||
public SignDisplay sign;
|
||||
public DecimalFormatSymbols symbols;
|
||||
public PaddingImpl padding;
|
||||
public DecimalMarkDisplay decimal;
|
||||
public IntegerWidthImpl integerWidth;
|
||||
|
||||
// Populated by notation/unit:
|
||||
public Modifier modOuter;
|
||||
public Modifier modMiddle;
|
||||
public Modifier modInner;
|
||||
public RoundingImpl rounding;
|
||||
public GroupingImpl grouping;
|
||||
public int multiplier;
|
||||
public boolean useCurrency;
|
||||
|
||||
private boolean frozen = false;
|
||||
|
||||
public void enableCloneInChain() {
|
||||
frozen = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuantityChain chain(QuantityChain parent) {
|
||||
// The MicroProps instance should always be at the top of the chain!
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MicroProps withQuantity(FormatQuantity quantity) {
|
||||
if (frozen) {
|
||||
return (MicroProps) this.clone();
|
||||
} else {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object clone() {
|
||||
try {
|
||||
return super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
}
|
39
icu4j/main/classes/core/src/newapi/impl/MultiplierImpl.java
Normal file
39
icu4j/main/classes/core/src/newapi/impl/MultiplierImpl.java
Normal file
@ -0,0 +1,39 @@
|
||||
// © 2017 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package newapi.impl;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import com.ibm.icu.impl.number.FormatQuantity;
|
||||
|
||||
public class MultiplierImpl implements QuantityChain {
|
||||
final int magnitudeMultiplier;
|
||||
final BigDecimal bigDecimalMultiplier;
|
||||
/* final */ QuantityChain parent;
|
||||
|
||||
public MultiplierImpl(int magnitudeMultiplier) {
|
||||
this.magnitudeMultiplier = magnitudeMultiplier;
|
||||
this.bigDecimalMultiplier = null;
|
||||
}
|
||||
|
||||
public MultiplierImpl(BigDecimal bigDecimalMultiplier) {
|
||||
this.magnitudeMultiplier = 0;
|
||||
this.bigDecimalMultiplier = bigDecimalMultiplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuantityChain chain(QuantityChain parent) {
|
||||
this.parent = parent;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MicroProps withQuantity(FormatQuantity quantity) {
|
||||
MicroProps micros = parent.withQuantity(quantity);
|
||||
quantity.adjustMagnitude(magnitudeMultiplier);
|
||||
if (bigDecimalMultiplier != null) {
|
||||
quantity.multiplyBy(bigDecimalMultiplier);
|
||||
}
|
||||
return micros;
|
||||
}
|
||||
}
|
414
icu4j/main/classes/core/src/newapi/impl/MurkyModifier.java
Normal file
414
icu4j/main/classes/core/src/newapi/impl/MurkyModifier.java
Normal file
@ -0,0 +1,414 @@
|
||||
// © 2017 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package newapi.impl;
|
||||
|
||||
import com.ibm.icu.impl.StandardPlural;
|
||||
import com.ibm.icu.impl.number.AffixPatternUtils;
|
||||
import com.ibm.icu.impl.number.AffixPatternUtils.SymbolProvider;
|
||||
import com.ibm.icu.impl.number.FormatQuantity;
|
||||
import com.ibm.icu.impl.number.Modifier;
|
||||
import com.ibm.icu.impl.number.NumberStringBuilder;
|
||||
import com.ibm.icu.impl.number.modifiers.ConstantMultiFieldModifier;
|
||||
import com.ibm.icu.text.DecimalFormatSymbols;
|
||||
import com.ibm.icu.text.MeasureFormat.FormatWidth;
|
||||
import com.ibm.icu.text.PluralRules;
|
||||
import com.ibm.icu.util.Currency;
|
||||
|
||||
import newapi.NumberFormatter.SignDisplay;
|
||||
|
||||
/**
|
||||
* This is a MUTABLE, NON-THREAD-SAFE class designed for performance. Do NOT save references to this
|
||||
* or attempt to use it from multiple threads!!!
|
||||
*
|
||||
* <p>This class takes a parsed pattern and returns a Modifier, without creating any objects. When
|
||||
* the Modifier methods are called, symbols are substituted directly into the output
|
||||
* NumberStringBuilder, without creating any intermediate Strings.
|
||||
*/
|
||||
public class MurkyModifier implements Modifier, SymbolProvider, CharSequence, QuantityChain {
|
||||
|
||||
// Modifier details
|
||||
final boolean isStrong;
|
||||
|
||||
// Pattern details
|
||||
AffixPatternProvider patternInfo;
|
||||
SignDisplay signDisplay;
|
||||
boolean perMilleReplacesPercent;
|
||||
|
||||
// Symbol details
|
||||
DecimalFormatSymbols symbols;
|
||||
FormatWidth unitWidth;
|
||||
String currency1;
|
||||
String currency2;
|
||||
String[] currency3;
|
||||
PluralRules rules;
|
||||
|
||||
// Number details
|
||||
boolean isNegative;
|
||||
StandardPlural plural;
|
||||
|
||||
// QuantityChain details
|
||||
QuantityChain parent;
|
||||
|
||||
// Transient CharSequence fields
|
||||
boolean inCharSequenceMode;
|
||||
int flags;
|
||||
int length;
|
||||
boolean prependSign;
|
||||
boolean plusReplacesMinusSign;
|
||||
|
||||
public MurkyModifier(boolean isStrong) {
|
||||
this.isStrong = isStrong;
|
||||
}
|
||||
|
||||
public void setPatternInfo(AffixPatternProvider patternInfo) {
|
||||
this.patternInfo = patternInfo;
|
||||
}
|
||||
|
||||
public void setPatternAttributes(SignDisplay signDisplay, boolean perMille) {
|
||||
this.signDisplay = signDisplay;
|
||||
this.perMilleReplacesPercent = perMille;
|
||||
}
|
||||
|
||||
public void setSymbols(
|
||||
DecimalFormatSymbols symbols, Currency currency, FormatWidth unitWidth, PluralRules rules) {
|
||||
assert (rules != null) == needsPlurals();
|
||||
this.symbols = symbols;
|
||||
this.unitWidth = unitWidth;
|
||||
this.rules = rules;
|
||||
|
||||
currency1 = currency.getName(symbols.getULocale(), Currency.SYMBOL_NAME, null);
|
||||
currency2 = currency.getCurrencyCode();
|
||||
|
||||
if (rules != null) {
|
||||
currency3 = new String[StandardPlural.COUNT];
|
||||
for (StandardPlural plural : StandardPlural.VALUES) {
|
||||
currency3[plural.ordinal()] =
|
||||
currency.getName(
|
||||
symbols.getULocale(), Currency.PLURAL_LONG_NAME, plural.getKeyword(), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setNumberProperties(boolean isNegative, StandardPlural plural) {
|
||||
assert (plural != null) == needsPlurals();
|
||||
this.isNegative = isNegative;
|
||||
this.plural = plural;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the pattern represented by this MurkyModifier requires a plural keyword in
|
||||
* order to localize. This is currently true only if there is a currency long name placeholder in
|
||||
* the pattern.
|
||||
*/
|
||||
public boolean needsPlurals() {
|
||||
return patternInfo.containsSymbolType(AffixPatternUtils.TYPE_CURRENCY_TRIPLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuantityChain chain(QuantityChain parent) {
|
||||
this.parent = parent;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MicroProps withQuantity(FormatQuantity fq) {
|
||||
MicroProps micros = parent.withQuantity(fq);
|
||||
if (needsPlurals()) {
|
||||
// TODO: Fix this. Avoid the copy.
|
||||
FormatQuantity copy = fq.createCopy();
|
||||
micros.rounding.apply(copy);
|
||||
setNumberProperties(fq.isNegative(), copy.getStandardPlural(rules));
|
||||
} else {
|
||||
setNumberProperties(fq.isNegative(), null);
|
||||
}
|
||||
micros.modMiddle = this;
|
||||
return micros;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new quantity-dependent Modifier that behaves the same as the current instance, but
|
||||
* which is immutable and can be saved for future use. The current instance is not changed by
|
||||
* calling this method except for the number properties.
|
||||
*
|
||||
* @return An immutable that supports both positive and negative numbers.
|
||||
*/
|
||||
public ImmutableMurkyModifier createImmutable() {
|
||||
NumberStringBuilder a = new NumberStringBuilder();
|
||||
NumberStringBuilder b = new NumberStringBuilder();
|
||||
if (needsPlurals()) {
|
||||
// Slower path when we require the plural keyword.
|
||||
Modifier[] mods = new Modifier[ImmutableMurkyModifierWithPlurals.getModsLength()];
|
||||
for (StandardPlural plural : StandardPlural.VALUES) {
|
||||
setNumberProperties(false, plural);
|
||||
Modifier positive = createConstantModifier(a, b);
|
||||
setNumberProperties(true, plural);
|
||||
Modifier negative = createConstantModifier(a, b);
|
||||
mods[ImmutableMurkyModifierWithPlurals.getModIndex(false, plural)] = positive;
|
||||
mods[ImmutableMurkyModifierWithPlurals.getModIndex(true, plural)] = negative;
|
||||
}
|
||||
return new ImmutableMurkyModifierWithPlurals(mods, rules);
|
||||
} else {
|
||||
// Faster path when plural keyword is not needed.
|
||||
setNumberProperties(false, null);
|
||||
Modifier positive = createConstantModifier(a, b);
|
||||
setNumberProperties(true, null);
|
||||
Modifier negative = createConstantModifier(a, b);
|
||||
return new ImmutableMurkyModifierWithoutPlurals(positive, negative);
|
||||
}
|
||||
}
|
||||
|
||||
private Modifier createConstantModifier(NumberStringBuilder a, NumberStringBuilder b) {
|
||||
insertPrefix(a.clear(), 0);
|
||||
insertSuffix(b.clear(), 0);
|
||||
if (patternInfo.hasCurrencySign()) {
|
||||
return new CurrencySpacingEnabledModifier(a, b, isStrong, symbols);
|
||||
} else {
|
||||
return new ConstantMultiFieldModifier(a, b, isStrong);
|
||||
}
|
||||
}
|
||||
|
||||
public static interface ImmutableMurkyModifier extends QuantityChain {
|
||||
public void applyToMicros(MicroProps micros, FormatQuantity quantity);
|
||||
}
|
||||
|
||||
public static class ImmutableMurkyModifierWithoutPlurals implements ImmutableMurkyModifier {
|
||||
final Modifier positive;
|
||||
final Modifier negative;
|
||||
/* final */ QuantityChain parent;
|
||||
|
||||
public ImmutableMurkyModifierWithoutPlurals(Modifier positive, Modifier negative) {
|
||||
this.positive = positive;
|
||||
this.negative = negative;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuantityChain chain(QuantityChain parent) {
|
||||
this.parent = parent;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MicroProps withQuantity(FormatQuantity quantity) {
|
||||
MicroProps micros = parent.withQuantity(quantity);
|
||||
applyToMicros(micros, quantity);
|
||||
return micros;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyToMicros(MicroProps micros, FormatQuantity quantity) {
|
||||
if (quantity.isNegative()) {
|
||||
micros.modMiddle = negative;
|
||||
} else {
|
||||
micros.modMiddle = positive;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class ImmutableMurkyModifierWithPlurals implements ImmutableMurkyModifier {
|
||||
final Modifier[] mods;
|
||||
final PluralRules rules;
|
||||
/* final */ QuantityChain parent;
|
||||
|
||||
public ImmutableMurkyModifierWithPlurals(Modifier[] mods, PluralRules rules) {
|
||||
assert mods.length == getModsLength();
|
||||
assert rules != null;
|
||||
this.mods = mods;
|
||||
this.rules = rules;
|
||||
}
|
||||
|
||||
public static int getModsLength() {
|
||||
return 2 * StandardPlural.COUNT;
|
||||
}
|
||||
|
||||
public static int getModIndex(boolean isNegative, StandardPlural plural) {
|
||||
return plural.ordinal() * 2 + (isNegative ? 1 : 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuantityChain chain(QuantityChain parent) {
|
||||
this.parent = parent;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MicroProps withQuantity(FormatQuantity quantity) {
|
||||
MicroProps micros = parent.withQuantity(quantity);
|
||||
applyToMicros(micros, quantity);
|
||||
return micros;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyToMicros(MicroProps micros, FormatQuantity quantity) {
|
||||
// TODO: Fix this. Avoid the copy.
|
||||
FormatQuantity copy = quantity.createCopy();
|
||||
copy.roundToInfinity();
|
||||
StandardPlural plural = copy.getStandardPlural(rules);
|
||||
Modifier mod = mods[getModIndex(quantity.isNegative(), plural)];
|
||||
micros.modMiddle = mod;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int apply(NumberStringBuilder output, int leftIndex, int rightIndex) {
|
||||
int prefixLen = insertPrefix(output, leftIndex);
|
||||
int suffixLen = insertSuffix(output, rightIndex + prefixLen);
|
||||
CurrencySpacingEnabledModifier.applyCurrencySpacing(
|
||||
output, leftIndex, prefixLen, rightIndex + prefixLen, suffixLen, symbols);
|
||||
return prefixLen + suffixLen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStrong() {
|
||||
return isStrong;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPrefix() {
|
||||
NumberStringBuilder sb = new NumberStringBuilder(10);
|
||||
insertPrefix(sb, 0);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSuffix() {
|
||||
NumberStringBuilder sb = new NumberStringBuilder(10);
|
||||
insertSuffix(sb, 0);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private int insertPrefix(NumberStringBuilder sb, int position) {
|
||||
enterCharSequenceMode(true);
|
||||
int length = AffixPatternUtils.unescape(this, sb, position, this);
|
||||
exitCharSequenceMode();
|
||||
return length;
|
||||
}
|
||||
|
||||
private int insertSuffix(NumberStringBuilder sb, int position) {
|
||||
enterCharSequenceMode(false);
|
||||
int length = AffixPatternUtils.unescape(this, sb, position, this);
|
||||
exitCharSequenceMode();
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getSymbol(int type) {
|
||||
switch (type) {
|
||||
case AffixPatternUtils.TYPE_MINUS_SIGN:
|
||||
return symbols.getMinusSignString();
|
||||
case AffixPatternUtils.TYPE_PLUS_SIGN:
|
||||
return symbols.getPlusSignString();
|
||||
case AffixPatternUtils.TYPE_PERCENT:
|
||||
return symbols.getPercentString();
|
||||
case AffixPatternUtils.TYPE_PERMILLE:
|
||||
return symbols.getPerMillString();
|
||||
case AffixPatternUtils.TYPE_CURRENCY_SINGLE:
|
||||
// FormatWidth ISO overrides the singular currency symbol
|
||||
if (unitWidth == FormatWidth.SHORT) {
|
||||
return currency2;
|
||||
} else {
|
||||
return currency1;
|
||||
}
|
||||
case AffixPatternUtils.TYPE_CURRENCY_DOUBLE:
|
||||
return currency2;
|
||||
case AffixPatternUtils.TYPE_CURRENCY_TRIPLE:
|
||||
// NOTE: This is the code path only for patterns containing "".
|
||||
// Most plural currencies are formatted in DataUtils.
|
||||
assert plural != null;
|
||||
if (currency3 == null) {
|
||||
return currency2;
|
||||
} else {
|
||||
return currency3[plural.ordinal()];
|
||||
}
|
||||
case AffixPatternUtils.TYPE_CURRENCY_QUAD:
|
||||
return "\uFFFD";
|
||||
case AffixPatternUtils.TYPE_CURRENCY_QUINT:
|
||||
return "\uFFFD";
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
/** This method contains the heart of the logic for rendering LDML affix strings. */
|
||||
private void enterCharSequenceMode(boolean isPrefix) {
|
||||
assert !inCharSequenceMode;
|
||||
inCharSequenceMode = true;
|
||||
|
||||
// Should the output render '+' where '-' would normally appear in the pattern?
|
||||
plusReplacesMinusSign =
|
||||
!isNegative
|
||||
&& signDisplay == SignDisplay.ALWAYS_SHOWN
|
||||
&& patternInfo.positiveHasPlusSign() == false;
|
||||
|
||||
// Should we use the negative affix pattern? (If not, we will use the positive one)
|
||||
boolean useNegativeAffixPattern =
|
||||
patternInfo.hasNegativeSubpattern()
|
||||
&& (isNegative || (patternInfo.negativeHasMinusSign() && plusReplacesMinusSign));
|
||||
|
||||
// Resolve the flags for the affix pattern.
|
||||
flags = 0;
|
||||
if (useNegativeAffixPattern) {
|
||||
flags |= AffixPatternProvider.Flags.NEGATIVE_SUBPATTERN;
|
||||
}
|
||||
if (isPrefix) {
|
||||
flags |= AffixPatternProvider.Flags.PREFIX;
|
||||
}
|
||||
if (plural != null) {
|
||||
assert plural.ordinal() == (AffixPatternProvider.Flags.PLURAL_MASK & plural.ordinal());
|
||||
flags |= plural.ordinal();
|
||||
}
|
||||
|
||||
// Should we prepend a sign to the pattern?
|
||||
if (!isPrefix || useNegativeAffixPattern) {
|
||||
prependSign = false;
|
||||
} else if (isNegative) {
|
||||
prependSign = signDisplay != SignDisplay.NEVER_SHOWN;
|
||||
} else {
|
||||
prependSign = plusReplacesMinusSign;
|
||||
}
|
||||
|
||||
// Finally, compute the length of the affix pattern.
|
||||
length = patternInfo.length(flags) + (prependSign ? 1 : 0);
|
||||
}
|
||||
|
||||
private void exitCharSequenceMode() {
|
||||
assert inCharSequenceMode;
|
||||
inCharSequenceMode = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int length() {
|
||||
if (inCharSequenceMode) {
|
||||
return length;
|
||||
} else {
|
||||
NumberStringBuilder sb = new NumberStringBuilder(20);
|
||||
apply(sb, 0, 0);
|
||||
return sb.length();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public char charAt(int index) {
|
||||
assert inCharSequenceMode;
|
||||
char candidate;
|
||||
if (prependSign && index == 0) {
|
||||
candidate = '-';
|
||||
} else if (prependSign) {
|
||||
candidate = patternInfo.charAt(flags, index - 1);
|
||||
} else {
|
||||
candidate = patternInfo.charAt(flags, index);
|
||||
}
|
||||
if (plusReplacesMinusSign && candidate == '-') {
|
||||
return '+';
|
||||
}
|
||||
if (perMilleReplacesPercent && candidate == '%') {
|
||||
return '‰';
|
||||
}
|
||||
return candidate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence subSequence(int start, int end) {
|
||||
// Should never be called in normal circumstances
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
81
icu4j/main/classes/core/src/newapi/impl/NotationImpl.java
Normal file
81
icu4j/main/classes/core/src/newapi/impl/NotationImpl.java
Normal file
@ -0,0 +1,81 @@
|
||||
// © 2017 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package newapi.impl;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
|
||||
|
||||
import newapi.NumberFormatter.NotationCompact;
|
||||
import newapi.NumberFormatter.NotationScientific;
|
||||
import newapi.NumberFormatter.SignDisplay;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class NotationImpl {
|
||||
|
||||
public static class NotationScientificImpl extends NotationScientific.Internal
|
||||
implements Cloneable {
|
||||
|
||||
int engineeringInterval;
|
||||
boolean requireMinInt;
|
||||
int minExponentDigits;
|
||||
SignDisplay exponentSignDisplay;
|
||||
|
||||
public NotationScientificImpl(int engineeringInterval) {
|
||||
this.engineeringInterval = engineeringInterval;
|
||||
requireMinInt = false;
|
||||
minExponentDigits = 1;
|
||||
exponentSignDisplay = SignDisplay.AUTO;
|
||||
}
|
||||
|
||||
public NotationScientificImpl(
|
||||
int engineeringInterval,
|
||||
boolean requireMinInt,
|
||||
int minExponentDigits,
|
||||
SignDisplay exponentSignDisplay) {
|
||||
this.engineeringInterval = engineeringInterval;
|
||||
this.requireMinInt = requireMinInt;
|
||||
this.minExponentDigits = minExponentDigits;
|
||||
this.exponentSignDisplay = exponentSignDisplay;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NotationScientific withMinExponentDigits(int minExponentDigits) {
|
||||
NotationScientificImpl other = (NotationScientificImpl) this.clone();
|
||||
other.minExponentDigits = minExponentDigits;
|
||||
return other;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NotationScientific withExponentSignDisplay(SignDisplay exponentSignDisplay) {
|
||||
NotationScientificImpl other = (NotationScientificImpl) this.clone();
|
||||
other.exponentSignDisplay = exponentSignDisplay;
|
||||
return other;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object clone() {
|
||||
try {
|
||||
return super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
// Should not happen since parent is Object
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class NotationCompactImpl extends NotationCompact.Internal {
|
||||
final CompactStyle compactStyle;
|
||||
final Map<String, Map<String, String>> compactCustomData;
|
||||
|
||||
public NotationCompactImpl(CompactStyle compactStyle) {
|
||||
compactCustomData = null;
|
||||
this.compactStyle = compactStyle;
|
||||
}
|
||||
|
||||
public NotationCompactImpl(Map<String, Map<String, String>> compactCustomData) {
|
||||
compactStyle = null;
|
||||
this.compactCustomData = compactCustomData;
|
||||
}
|
||||
}
|
||||
}
|
328
icu4j/main/classes/core/src/newapi/impl/NumberFormatterImpl.java
Normal file
328
icu4j/main/classes/core/src/newapi/impl/NumberFormatterImpl.java
Normal file
@ -0,0 +1,328 @@
|
||||
// © 2017 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package newapi.impl;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import com.ibm.icu.impl.number.FormatQuantity4;
|
||||
import com.ibm.icu.impl.number.FormatQuantityBCD;
|
||||
import com.ibm.icu.impl.number.NumberStringBuilder;
|
||||
import com.ibm.icu.impl.number.PatternString;
|
||||
import com.ibm.icu.impl.number.Properties;
|
||||
import com.ibm.icu.text.DecimalFormatSymbols;
|
||||
import com.ibm.icu.text.MeasureFormat.FormatWidth;
|
||||
import com.ibm.icu.text.NumberingSystem;
|
||||
import com.ibm.icu.util.Measure;
|
||||
import com.ibm.icu.util.MeasureUnit;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
|
||||
import newapi.NumberFormatter;
|
||||
import newapi.NumberFormatter.DecimalMarkDisplay;
|
||||
import newapi.NumberFormatter.IGrouping;
|
||||
import newapi.NumberFormatter.IRounding;
|
||||
import newapi.NumberFormatter.IntegerWidth;
|
||||
import newapi.NumberFormatter.Notation;
|
||||
import newapi.NumberFormatter.NumberFormatterResult;
|
||||
import newapi.NumberFormatter.Padding;
|
||||
import newapi.NumberFormatter.SignDisplay;
|
||||
import newapi.NumberFormatter.UnlocalizedNumberFormatter;
|
||||
|
||||
/** @author sffc */
|
||||
public class NumberFormatterImpl extends NumberFormatter.LocalizedNumberFormatter.Internal {
|
||||
|
||||
private static final NumberFormatterImpl BASE = new NumberFormatterImpl();
|
||||
|
||||
// TODO: Set a good value here.
|
||||
static final int DEFAULT_THRESHOLD = 3;
|
||||
|
||||
static final int KEY_MACROS = 0;
|
||||
static final int KEY_LOCALE = 1;
|
||||
static final int KEY_NOTATION = 2;
|
||||
static final int KEY_UNIT = 3;
|
||||
static final int KEY_ROUNDING = 4;
|
||||
static final int KEY_GROUPING = 5;
|
||||
static final int KEY_PADDING = 6;
|
||||
static final int KEY_INTEGER = 7;
|
||||
static final int KEY_SYMBOLS = 8;
|
||||
static final int KEY_UNIT_WIDTH = 9;
|
||||
static final int KEY_SIGN = 10;
|
||||
static final int KEY_DECIMAL = 11;
|
||||
static final int KEY_MAX = 12;
|
||||
|
||||
public static NumberFormatterImpl with() {
|
||||
return BASE;
|
||||
}
|
||||
|
||||
/** Internal method to set a starting macros. */
|
||||
public static NumberFormatterImpl fromMacros(MacroProps macros) {
|
||||
return new NumberFormatterImpl(BASE, KEY_MACROS, macros);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to construct a chain from a pattern using {@link NumberPropertyMapper}. Could
|
||||
* be added to the public API if the feature is requested. In that case, a more efficient
|
||||
* implementation may be desired.
|
||||
*/
|
||||
public static UnlocalizedNumberFormatter fromPattern(
|
||||
String string, DecimalFormatSymbols symbols) {
|
||||
Properties props = PatternString.parseToProperties(string);
|
||||
MacroProps macros = NumberPropertyMapper.oldToNew(props, symbols, null);
|
||||
return fromMacros(macros);
|
||||
}
|
||||
|
||||
// TODO: Reduce the number of fields.
|
||||
final NumberFormatterImpl parent;
|
||||
final int key;
|
||||
final Object value;
|
||||
volatile MacroProps resolvedMacros;
|
||||
volatile AtomicInteger callCount;
|
||||
volatile NumberFormatterImpl savedWithUnit;
|
||||
volatile Worker1 compiled;
|
||||
|
||||
/** Base constructor; called during startup only */
|
||||
private NumberFormatterImpl() {
|
||||
parent = null;
|
||||
key = -1;
|
||||
value = null;
|
||||
}
|
||||
|
||||
/** Primary constructor */
|
||||
private NumberFormatterImpl(NumberFormatterImpl parent, int key, Object value) {
|
||||
this.parent = parent;
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberFormatterImpl notation(Notation notation) {
|
||||
return new NumberFormatterImpl(this, KEY_NOTATION, notation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberFormatterImpl unit(MeasureUnit unit) {
|
||||
return new NumberFormatterImpl(this, KEY_UNIT, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberFormatterImpl rounding(IRounding rounding) {
|
||||
return new NumberFormatterImpl(this, KEY_ROUNDING, rounding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberFormatterImpl grouping(IGrouping grouping) {
|
||||
return new NumberFormatterImpl(this, KEY_GROUPING, grouping);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberFormatterImpl padding(Padding padding) {
|
||||
return new NumberFormatterImpl(this, KEY_PADDING, padding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberFormatterImpl integerWidth(IntegerWidth style) {
|
||||
return new NumberFormatterImpl(this, KEY_INTEGER, style);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberFormatterImpl symbols(DecimalFormatSymbols symbols) {
|
||||
return new NumberFormatterImpl(this, KEY_SYMBOLS, symbols);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberFormatterImpl symbols(NumberingSystem ns) {
|
||||
return new NumberFormatterImpl(this, KEY_SYMBOLS, ns);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberFormatterImpl unitWidth(FormatWidth style) {
|
||||
return new NumberFormatterImpl(this, KEY_UNIT_WIDTH, style);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberFormatterImpl sign(SignDisplay style) {
|
||||
return new NumberFormatterImpl(this, KEY_SIGN, style);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberFormatterImpl decimal(DecimalMarkDisplay style) {
|
||||
return new NumberFormatterImpl(this, KEY_DECIMAL, style);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberFormatterImpl locale(Locale locale) {
|
||||
return new NumberFormatterImpl(this, KEY_LOCALE, ULocale.forLocale(locale));
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberFormatterImpl locale(ULocale locale) {
|
||||
return new NumberFormatterImpl(this, KEY_LOCALE, locale);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toSkeleton() {
|
||||
return SkeletonBuilder.macrosToSkeleton(resolve());
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberFormatterResult format(long input) {
|
||||
return format(new FormatQuantity4(input), DEFAULT_THRESHOLD);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberFormatterResult format(double input) {
|
||||
return format(new FormatQuantity4(input), DEFAULT_THRESHOLD);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberFormatterResult format(Number input) {
|
||||
return format(new FormatQuantity4(input), DEFAULT_THRESHOLD);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberFormatterResult format(Measure input) {
|
||||
return formatWithThreshold(input, DEFAULT_THRESHOLD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal version of format with support for a custom regulation threshold. A threshold of 1
|
||||
* causes the data structures to be built right away. A threshold of 0 prevents the data
|
||||
* structures from being built.
|
||||
*/
|
||||
public NumberFormatterResult formatWithThreshold(Number number, int threshold) {
|
||||
return format(new FormatQuantity4(number), threshold);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal version of format with support for a custom regulation threshold. A threshold of 1
|
||||
* causes the data structures to be built right away. A threshold of 0 prevents the data
|
||||
* structures from being built.
|
||||
*/
|
||||
public NumberFormatterResult formatWithThreshold(Measure input, int threshold) {
|
||||
MeasureUnit unit = input.getUnit();
|
||||
Number number = input.getNumber();
|
||||
// Use this formatter if possible
|
||||
if (Objects.equals(resolve().unit, unit)) {
|
||||
return formatWithThreshold(number, threshold);
|
||||
}
|
||||
// This mechanism saves the previously used unit, so if the user calls this method with the
|
||||
// same unit multiple times in a row, they get a more efficient code path.
|
||||
NumberFormatterImpl withUnit = savedWithUnit;
|
||||
if (withUnit == null || !Objects.equals(withUnit.resolve().unit, unit)) {
|
||||
withUnit = new NumberFormatterImpl(this, KEY_UNIT, unit);
|
||||
savedWithUnit = withUnit;
|
||||
}
|
||||
return withUnit.formatWithThreshold(number, threshold);
|
||||
}
|
||||
|
||||
private NumberFormatterResult format(FormatQuantityBCD fq, int threshold) {
|
||||
NumberStringBuilder string = new NumberStringBuilder();
|
||||
// Lazily create the AtomicInteger
|
||||
if (callCount == null) {
|
||||
callCount = new AtomicInteger();
|
||||
}
|
||||
int currentCount = callCount.incrementAndGet();
|
||||
MicroProps micros;
|
||||
if (currentCount == threshold) {
|
||||
compiled = Worker1.fromMacros(resolve());
|
||||
micros = compiled.apply(fq, string);
|
||||
} else if (compiled != null) {
|
||||
micros = compiled.apply(fq, string);
|
||||
} else {
|
||||
micros = Worker1.applyStatic(resolve(), fq, string);
|
||||
}
|
||||
return new NumberFormatterResult(string, fq, micros);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return resolve().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (this == other) return true;
|
||||
if (other == null) return false;
|
||||
if (!(other instanceof NumberFormatterImpl)) return false;
|
||||
return resolve().equals(((NumberFormatterImpl) other).resolve());
|
||||
}
|
||||
|
||||
private MacroProps resolve() {
|
||||
if (resolvedMacros != null) {
|
||||
return resolvedMacros;
|
||||
}
|
||||
// Although the linked-list fluent storage approach requires this method,
|
||||
// my benchmarks show that linked-list is still faster than a full clone
|
||||
// of a MacroProps object at each step.
|
||||
MacroProps macros = new MacroProps();
|
||||
NumberFormatterImpl current = this;
|
||||
while (current != BASE) {
|
||||
switch (current.key) {
|
||||
case KEY_MACROS:
|
||||
macros.fallback((MacroProps) current.value);
|
||||
break;
|
||||
case KEY_LOCALE:
|
||||
if (macros.loc == null) {
|
||||
macros.loc = (ULocale) current.value;
|
||||
}
|
||||
break;
|
||||
case KEY_NOTATION:
|
||||
if (macros.notation == null) {
|
||||
macros.notation = (Notation) current.value;
|
||||
}
|
||||
break;
|
||||
case KEY_UNIT:
|
||||
if (macros.unit == null) {
|
||||
macros.unit = (MeasureUnit) current.value;
|
||||
}
|
||||
break;
|
||||
case KEY_ROUNDING:
|
||||
if (macros.rounding == null) {
|
||||
macros.rounding = (IRounding) current.value;
|
||||
}
|
||||
break;
|
||||
case KEY_GROUPING:
|
||||
if (macros.grouping == null) {
|
||||
macros.grouping = (IGrouping) current.value;
|
||||
}
|
||||
break;
|
||||
case KEY_PADDING:
|
||||
if (macros.padding == null) {
|
||||
macros.padding = (Padding) current.value;
|
||||
}
|
||||
break;
|
||||
case KEY_INTEGER:
|
||||
if (macros.integerWidth == null) {
|
||||
macros.integerWidth = (IntegerWidth) current.value;
|
||||
}
|
||||
break;
|
||||
case KEY_SYMBOLS:
|
||||
if (macros.symbols == null) {
|
||||
macros.symbols = /*(Object)*/ current.value;
|
||||
}
|
||||
break;
|
||||
case KEY_UNIT_WIDTH:
|
||||
if (macros.unitWidth == null) {
|
||||
macros.unitWidth = (FormatWidth) current.value;
|
||||
}
|
||||
break;
|
||||
case KEY_SIGN:
|
||||
if (macros.sign == null) {
|
||||
macros.sign = (SignDisplay) current.value;
|
||||
}
|
||||
break;
|
||||
case KEY_DECIMAL:
|
||||
if (macros.decimal == null) {
|
||||
macros.decimal = (DecimalMarkDisplay) current.value;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
current = current.parent;
|
||||
}
|
||||
resolvedMacros = macros;
|
||||
return macros;
|
||||
}
|
||||
}
|
@ -0,0 +1,447 @@
|
||||
// © 2017 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package newapi.impl;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
|
||||
import com.ibm.icu.impl.StandardPlural;
|
||||
import com.ibm.icu.impl.number.AffixPatternUtils;
|
||||
import com.ibm.icu.impl.number.LdmlPatternInfo;
|
||||
import com.ibm.icu.impl.number.LdmlPatternInfo.PatternParseResult;
|
||||
import com.ibm.icu.impl.number.Properties;
|
||||
import com.ibm.icu.impl.number.RoundingUtils;
|
||||
import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
|
||||
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;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
|
||||
import newapi.NumberFormatter.CurrencyRounding;
|
||||
import newapi.NumberFormatter.DecimalMarkDisplay;
|
||||
import newapi.NumberFormatter.IntegerWidth;
|
||||
import newapi.NumberFormatter.Notation;
|
||||
import newapi.NumberFormatter.Rounding;
|
||||
import newapi.NumberFormatter.SignDisplay;
|
||||
import newapi.impl.NotationImpl.NotationScientificImpl;
|
||||
import newapi.impl.RoundingImpl.RoundingImplCurrency;
|
||||
import newapi.impl.RoundingImpl.RoundingImplFraction;
|
||||
import newapi.impl.RoundingImpl.RoundingImplIncrement;
|
||||
import newapi.impl.RoundingImpl.RoundingImplSignificant;
|
||||
|
||||
/** @author sffc */
|
||||
public final class NumberPropertyMapper {
|
||||
|
||||
/**
|
||||
* Creates a new {@link MacroProps} object based on the content of a {@link Properties} object. In
|
||||
* other words, maps Properties to MacroProps. This function is used by the JDK-compatibility API
|
||||
* to call into the ICU 60 fluent number formatting pipeline.
|
||||
*
|
||||
* @param properties The property bag to be mapped.
|
||||
* @param symbols The symbols associated with the property bag.
|
||||
* @param exportedProperties A property bag in which to store validated properties.
|
||||
* @return A new MacroProps containing all of the information in the Properties.
|
||||
*/
|
||||
public static MacroProps oldToNew(
|
||||
Properties properties, DecimalFormatSymbols symbols, Properties exportedProperties) {
|
||||
MacroProps macros = new MacroProps();
|
||||
ULocale locale = symbols.getULocale();
|
||||
|
||||
/////////////
|
||||
// SYMBOLS //
|
||||
/////////////
|
||||
|
||||
macros.symbols = symbols;
|
||||
|
||||
//////////////////
|
||||
// PLURAL RULES //
|
||||
//////////////////
|
||||
|
||||
macros.rules = properties.getPluralRules();
|
||||
|
||||
/////////////
|
||||
// AFFIXES //
|
||||
/////////////
|
||||
|
||||
AffixPatternProvider affixProvider;
|
||||
if (properties.getCurrencyPluralInfo() == null) {
|
||||
affixProvider =
|
||||
new PropertiesAffixPatternProvider(
|
||||
properties.getPositivePrefix() != null
|
||||
? AffixPatternUtils.escape(properties.getPositivePrefix())
|
||||
: properties.getPositivePrefixPattern(),
|
||||
properties.getPositiveSuffix() != null
|
||||
? AffixPatternUtils.escape(properties.getPositiveSuffix())
|
||||
: properties.getPositiveSuffixPattern(),
|
||||
properties.getNegativePrefix() != null
|
||||
? AffixPatternUtils.escape(properties.getNegativePrefix())
|
||||
: properties.getNegativePrefixPattern(),
|
||||
properties.getNegativeSuffix() != null
|
||||
? AffixPatternUtils.escape(properties.getNegativeSuffix())
|
||||
: properties.getNegativeSuffixPattern());
|
||||
} else {
|
||||
affixProvider = new CurrencyPluralInfoAffixProvider(properties.getCurrencyPluralInfo());
|
||||
}
|
||||
macros.affixProvider = affixProvider;
|
||||
|
||||
///////////
|
||||
// UNITS //
|
||||
///////////
|
||||
|
||||
boolean useCurrency =
|
||||
((properties.getCurrency() != null)
|
||||
|| properties.getCurrencyPluralInfo() != null
|
||||
|| properties.getCurrencyUsage() != null
|
||||
|| affixProvider.hasCurrencySign());
|
||||
Currency currency = CustomSymbolCurrency.resolve(properties.getCurrency(), locale, symbols);
|
||||
CurrencyUsage currencyUsage = properties.getCurrencyUsage();
|
||||
boolean explicitCurrencyUsage = currencyUsage != Properties.DEFAULT_CURRENCY_USAGE;
|
||||
if (!explicitCurrencyUsage) {
|
||||
currencyUsage = CurrencyUsage.STANDARD;
|
||||
}
|
||||
if (useCurrency) {
|
||||
macros.unit = currency;
|
||||
}
|
||||
|
||||
///////////////////////
|
||||
// ROUNDING STRATEGY //
|
||||
///////////////////////
|
||||
|
||||
int maxInt = properties.getMaximumIntegerDigits();
|
||||
int minInt = properties.getMinimumIntegerDigits();
|
||||
int maxFrac = properties.getMaximumFractionDigits();
|
||||
int minFrac = properties.getMinimumFractionDigits();
|
||||
int minSig = properties.getMinimumSignificantDigits();
|
||||
int maxSig = properties.getMaximumSignificantDigits();
|
||||
BigDecimal roundingIncrement = properties.getRoundingIncrement();
|
||||
MathContext mathContext = RoundingUtils.getMathContextOrUnlimited(properties);
|
||||
boolean explicitMinMaxFrac =
|
||||
minFrac != Properties.DEFAULT_MINIMUM_FRACTION_DIGITS
|
||||
|| maxFrac != Properties.DEFAULT_MAXIMUM_FRACTION_DIGITS;
|
||||
boolean explicitMinMaxSig =
|
||||
minSig != Properties.DEFAULT_MINIMUM_SIGNIFICANT_DIGITS
|
||||
|| maxSig != Properties.DEFAULT_MAXIMUM_SIGNIFICANT_DIGITS;
|
||||
// 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 after 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 before 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;
|
||||
}
|
||||
Rounding rounding = null;
|
||||
if (explicitCurrencyUsage) {
|
||||
rounding = RoundingImplCurrency.getInstance(currencyUsage).withCurrency(currency);
|
||||
} else if (roundingIncrement != null) {
|
||||
rounding = RoundingImplIncrement.getInstance(roundingIncrement);
|
||||
} else if (explicitMinMaxSig) {
|
||||
minSig = minSig < 1 ? 1 : minSig > 1000 ? 1000 : minSig;
|
||||
maxSig = maxSig < 0 ? 1000 : maxSig < minSig ? minSig : maxSig > 1000 ? 1000 : maxSig;
|
||||
rounding = RoundingImplSignificant.getInstance(minSig, maxSig);
|
||||
} else if (explicitMinMaxFrac) {
|
||||
rounding = RoundingImplFraction.getInstance(minFrac, maxFrac);
|
||||
} else if (useCurrency) {
|
||||
rounding = RoundingImplCurrency.getInstance(currencyUsage);
|
||||
}
|
||||
if (rounding != null) {
|
||||
rounding = rounding.withMode(mathContext);
|
||||
macros.rounding = rounding;
|
||||
}
|
||||
|
||||
///////////////////
|
||||
// INTEGER WIDTH //
|
||||
///////////////////
|
||||
|
||||
macros.integerWidth = IntegerWidth.zeroFillTo(minInt).truncateAt(maxInt);
|
||||
|
||||
///////////////////////
|
||||
// GROUPING STRATEGY //
|
||||
///////////////////////
|
||||
|
||||
int grouping1 = properties.getGroupingSize();
|
||||
int grouping2 = properties.getSecondaryGroupingSize();
|
||||
int minGrouping = properties.getMinimumGroupingDigits();
|
||||
assert grouping1 >= -2; // value of -2 means to forward no grouping information
|
||||
grouping1 = grouping1 > 0 ? grouping1 : grouping2 > 0 ? grouping2 : grouping1;
|
||||
grouping2 = grouping2 > 0 ? grouping2 : grouping1;
|
||||
// TODO: Is it important to handle minGrouping > 2?
|
||||
macros.grouping =
|
||||
GroupingImpl.getInstance((short) grouping1, (short) grouping2, minGrouping == 2);
|
||||
|
||||
/////////////
|
||||
// PADDING //
|
||||
/////////////
|
||||
|
||||
if (properties.getFormatWidth() != Properties.DEFAULT_FORMAT_WIDTH) {
|
||||
macros.padding =
|
||||
PaddingImpl.getInstance(
|
||||
properties.getPadString(), properties.getFormatWidth(), properties.getPadPosition());
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
// DECIMAL MARK ALWAYS SHOWN //
|
||||
///////////////////////////////
|
||||
|
||||
macros.decimal =
|
||||
properties.getDecimalSeparatorAlwaysShown()
|
||||
? DecimalMarkDisplay.ALWAYS_SHOWN
|
||||
: DecimalMarkDisplay.AUTO;
|
||||
|
||||
///////////////////////
|
||||
// SIGN ALWAYS SHOWN //
|
||||
///////////////////////
|
||||
|
||||
macros.sign = properties.getSignAlwaysShown() ? SignDisplay.ALWAYS_SHOWN : SignDisplay.AUTO;
|
||||
|
||||
/////////////////////////
|
||||
// SCIENTIFIC NOTATION //
|
||||
/////////////////////////
|
||||
|
||||
if (properties.getMinimumExponentDigits() != Properties.DEFAULT_MINIMUM_EXPONENT_DIGITS) {
|
||||
// Scientific notation is required.
|
||||
// The mapping from property bag to scientific notation is nontrivial due to LDML rules.
|
||||
// The maximum of 8 engineering digits has unknown origins and is not in the spec.
|
||||
int engineering =
|
||||
(maxInt != Integer.MAX_VALUE) ? maxInt : properties.getMaximumIntegerDigits();
|
||||
engineering = (engineering < 0) ? 0 : (engineering > 8) ? minInt : engineering;
|
||||
macros.notation =
|
||||
new NotationScientificImpl(
|
||||
// Engineering interval:
|
||||
engineering,
|
||||
// Enforce minimum integer digits (for patterns like "000.00E0"):
|
||||
(engineering == minInt),
|
||||
// Minimum exponent digits:
|
||||
properties.getMinimumExponentDigits(),
|
||||
// Exponent sign always shown:
|
||||
properties.getExponentSignAlwaysShown()
|
||||
? SignDisplay.ALWAYS_SHOWN
|
||||
: SignDisplay.AUTO);
|
||||
// Scientific notation also involves overriding the rounding mode.
|
||||
if (macros.rounding instanceof RoundingImplFraction) {
|
||||
int minInt_ = properties.getMinimumIntegerDigits();
|
||||
int minFrac_ = properties.getMinimumFractionDigits();
|
||||
int maxFrac_ = properties.getMaximumFractionDigits();
|
||||
if (minInt_ == 0 && maxFrac_ == 0) {
|
||||
// Patterns like "#E0" and "##E0", which mean no rounding!
|
||||
macros.rounding = Rounding.NONE.withMode(mathContext);
|
||||
} else if (minInt_ == 0 && minFrac_ == 0) {
|
||||
// Patterns like "#.##E0" (no zeros in the mantissa), which mean round to maxFrac+1
|
||||
macros.rounding = new RoundingImplSignificant(1, maxFrac_ + 1).withMode(mathContext);
|
||||
} else {
|
||||
// All other scientific patterns, which mean round to minInt+maxFrac
|
||||
macros.rounding =
|
||||
new RoundingImplSignificant(minInt_ + minFrac_, minInt_ + maxFrac_)
|
||||
.withMode(mathContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
// COMPACT NOTATION //
|
||||
//////////////////////
|
||||
|
||||
if (properties.getCompactStyle() != Properties.DEFAULT_COMPACT_STYLE) {
|
||||
if (properties.getCompactCustomData() != null) {
|
||||
macros.notation = new NotationImpl.NotationCompactImpl(properties.getCompactCustomData());
|
||||
} else if (properties.getCompactStyle() == CompactStyle.LONG) {
|
||||
macros.notation = Notation.COMPACT_LONG;
|
||||
} else {
|
||||
macros.notation = Notation.COMPACT_SHORT;
|
||||
}
|
||||
// Do not forward the affix provider.
|
||||
macros.affixProvider = null;
|
||||
}
|
||||
|
||||
/////////////////
|
||||
// MULTIPLIERS //
|
||||
/////////////////
|
||||
|
||||
if (properties.getMagnitudeMultiplier() != Properties.DEFAULT_MAGNITUDE_MULTIPLIER) {
|
||||
macros.multiplier = new MultiplierImpl(properties.getMagnitudeMultiplier());
|
||||
} else if (properties.getMultiplier() != Properties.DEFAULT_MULTIPLIER) {
|
||||
macros.multiplier = new MultiplierImpl(properties.getMultiplier());
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
// PROPERTY EXPORTS //
|
||||
//////////////////////
|
||||
|
||||
if (exportedProperties != null) {
|
||||
|
||||
exportedProperties.setMathContext(mathContext);
|
||||
exportedProperties.setRoundingMode(mathContext.getRoundingMode());
|
||||
exportedProperties.setMinimumIntegerDigits(minInt);
|
||||
exportedProperties.setMaximumIntegerDigits(maxInt);
|
||||
|
||||
Rounding rounding_;
|
||||
if (rounding instanceof CurrencyRounding) {
|
||||
rounding_ = ((CurrencyRounding) rounding).withCurrency(currency);
|
||||
} else {
|
||||
rounding_ = rounding;
|
||||
}
|
||||
int minFrac_ = minFrac;
|
||||
int maxFrac_ = maxFrac;
|
||||
int minSig_ = minSig;
|
||||
int maxSig_ = maxSig;
|
||||
BigDecimal increment_ = null;
|
||||
if (rounding_ instanceof RoundingImplFraction) {
|
||||
minFrac_ = ((RoundingImplFraction) rounding_).minFrac;
|
||||
maxFrac_ = ((RoundingImplFraction) rounding_).maxFrac;
|
||||
} else if (rounding_ instanceof RoundingImplIncrement) {
|
||||
increment_ = ((RoundingImplIncrement) rounding_).increment;
|
||||
minFrac_ = increment_.scale();
|
||||
maxFrac_ = increment_.scale();
|
||||
} else if (rounding_ instanceof RoundingImplSignificant) {
|
||||
minSig_ = ((RoundingImplSignificant) rounding_).minSig;
|
||||
maxSig_ = ((RoundingImplSignificant) rounding_).maxSig;
|
||||
}
|
||||
|
||||
exportedProperties.setMinimumFractionDigits(minFrac_);
|
||||
exportedProperties.setMaximumFractionDigits(maxFrac_);
|
||||
exportedProperties.setMinimumSignificantDigits(minSig_);
|
||||
exportedProperties.setMaximumSignificantDigits(maxSig_);
|
||||
exportedProperties.setRoundingIncrement(increment_);
|
||||
}
|
||||
|
||||
return macros;
|
||||
}
|
||||
|
||||
private static class PropertiesAffixPatternProvider implements AffixPatternProvider {
|
||||
private final String posPrefixPattern;
|
||||
private final String posSuffixPattern;
|
||||
private final String negPrefixPattern;
|
||||
private final String negSuffixPattern;
|
||||
|
||||
public PropertiesAffixPatternProvider(String ppp, String psp, String npp, String nsp) {
|
||||
if (ppp == null) ppp = "";
|
||||
if (psp == null) psp = "";
|
||||
if (npp == null && nsp != null) npp = "-"; // TODO: This is a hack.
|
||||
if (nsp == null && npp != null) nsp = "";
|
||||
posPrefixPattern = ppp;
|
||||
posSuffixPattern = psp;
|
||||
negPrefixPattern = npp;
|
||||
negSuffixPattern = nsp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char charAt(int flags, int i) {
|
||||
boolean prefix = (flags & Flags.PREFIX) != 0;
|
||||
boolean negative = (flags & Flags.NEGATIVE_SUBPATTERN) != 0;
|
||||
if (prefix && negative) {
|
||||
return negPrefixPattern.charAt(i);
|
||||
} else if (prefix) {
|
||||
return posPrefixPattern.charAt(i);
|
||||
} else if (negative) {
|
||||
return negSuffixPattern.charAt(i);
|
||||
} else {
|
||||
return posSuffixPattern.charAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int length(int flags) {
|
||||
boolean prefix = (flags & Flags.PREFIX) != 0;
|
||||
boolean negative = (flags & Flags.NEGATIVE_SUBPATTERN) != 0;
|
||||
if (prefix && negative) {
|
||||
return negPrefixPattern.length();
|
||||
} else if (prefix) {
|
||||
return posPrefixPattern.length();
|
||||
} else if (negative) {
|
||||
return negSuffixPattern.length();
|
||||
} else {
|
||||
return posSuffixPattern.length();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean positiveHasPlusSign() {
|
||||
return AffixPatternUtils.containsType(posPrefixPattern, AffixPatternUtils.TYPE_PLUS_SIGN)
|
||||
|| AffixPatternUtils.containsType(posSuffixPattern, AffixPatternUtils.TYPE_PLUS_SIGN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNegativeSubpattern() {
|
||||
return negPrefixPattern != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean negativeHasMinusSign() {
|
||||
return AffixPatternUtils.containsType(negPrefixPattern, AffixPatternUtils.TYPE_MINUS_SIGN)
|
||||
|| AffixPatternUtils.containsType(negSuffixPattern, AffixPatternUtils.TYPE_MINUS_SIGN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCurrencySign() {
|
||||
return AffixPatternUtils.hasCurrencySymbols(posPrefixPattern)
|
||||
|| AffixPatternUtils.hasCurrencySymbols(posSuffixPattern)
|
||||
|| AffixPatternUtils.hasCurrencySymbols(negPrefixPattern)
|
||||
|| AffixPatternUtils.hasCurrencySymbols(negSuffixPattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsSymbolType(int type) {
|
||||
return AffixPatternUtils.containsType(posPrefixPattern, type)
|
||||
|| AffixPatternUtils.containsType(posSuffixPattern, type)
|
||||
|| AffixPatternUtils.containsType(negPrefixPattern, type)
|
||||
|| AffixPatternUtils.containsType(negSuffixPattern, type);
|
||||
}
|
||||
}
|
||||
|
||||
private static class CurrencyPluralInfoAffixProvider implements AffixPatternProvider {
|
||||
private final AffixPatternProvider[] affixesByPlural;
|
||||
|
||||
public CurrencyPluralInfoAffixProvider(CurrencyPluralInfo cpi) {
|
||||
affixesByPlural = new PatternParseResult[StandardPlural.COUNT];
|
||||
for (StandardPlural plural : StandardPlural.VALUES) {
|
||||
affixesByPlural[plural.ordinal()] =
|
||||
LdmlPatternInfo.parse(cpi.getCurrencyPluralPattern(plural.getKeyword()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public char charAt(int flags, int i) {
|
||||
int pluralOrdinal = (flags & Flags.PLURAL_MASK);
|
||||
return affixesByPlural[pluralOrdinal].charAt(flags, i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int length(int flags) {
|
||||
int pluralOrdinal = (flags & Flags.PLURAL_MASK);
|
||||
return affixesByPlural[pluralOrdinal].length(flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean positiveHasPlusSign() {
|
||||
return affixesByPlural[StandardPlural.OTHER.ordinal()].positiveHasPlusSign();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNegativeSubpattern() {
|
||||
return affixesByPlural[StandardPlural.OTHER.ordinal()].hasNegativeSubpattern();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean negativeHasMinusSign() {
|
||||
return affixesByPlural[StandardPlural.OTHER.ordinal()].negativeHasMinusSign();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasCurrencySign() {
|
||||
return affixesByPlural[StandardPlural.OTHER.ordinal()].hasCurrencySign();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsSymbolType(int type) {
|
||||
return affixesByPlural[StandardPlural.OTHER.ordinal()].containsSymbolType(type);
|
||||
}
|
||||
}
|
||||
}
|
109
icu4j/main/classes/core/src/newapi/impl/PaddingImpl.java
Normal file
109
icu4j/main/classes/core/src/newapi/impl/PaddingImpl.java
Normal file
@ -0,0 +1,109 @@
|
||||
// © 2017 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package newapi.impl;
|
||||
|
||||
import com.ibm.icu.impl.number.NumberStringBuilder;
|
||||
import com.ibm.icu.impl.number.formatters.PaddingFormat.PadPosition;
|
||||
|
||||
import newapi.NumberFormatter.Padding;
|
||||
|
||||
public class PaddingImpl extends Padding.Internal {
|
||||
|
||||
String paddingString;
|
||||
int targetWidth;
|
||||
PadPosition position;
|
||||
|
||||
public static final PaddingImpl NONE = new PaddingImpl();
|
||||
|
||||
public static PaddingImpl getInstance(
|
||||
String paddingString, int targetWidth, PadPosition position) {
|
||||
// TODO: Add a few default implementations
|
||||
return new PaddingImpl(paddingString, targetWidth, position);
|
||||
}
|
||||
|
||||
/** Default constructor, producing an empty instance */
|
||||
public PaddingImpl() {
|
||||
paddingString = null;
|
||||
targetWidth = -1;
|
||||
position = null;
|
||||
}
|
||||
|
||||
private PaddingImpl(String paddingString, int targetWidth, PadPosition position) {
|
||||
this.paddingString = (paddingString == null) ? " " : paddingString;
|
||||
this.targetWidth = targetWidth;
|
||||
this.position = (position == null) ? PadPosition.BEFORE_PREFIX : position;
|
||||
}
|
||||
|
||||
public int applyModsAndMaybePad(
|
||||
MicroProps micros, NumberStringBuilder string, int leftIndex, int rightIndex) {
|
||||
// Apply modInner (scientific notation) before padding
|
||||
int innerLength = micros.modInner.apply(string, leftIndex, rightIndex);
|
||||
|
||||
// No padding; apply the mods and leave.
|
||||
if (targetWidth < 0) {
|
||||
return applyMicroMods(micros, string, leftIndex, rightIndex + innerLength);
|
||||
}
|
||||
|
||||
// Estimate the padding width needed.
|
||||
// TODO: Make this more efficient (less copying)
|
||||
// TODO: How to handle when padding is inserted between a currency sign and the number
|
||||
// when currency spacing is in play?
|
||||
NumberStringBuilder backup = new NumberStringBuilder(string);
|
||||
int length = innerLength + applyMicroMods(micros, string, leftIndex, rightIndex + innerLength);
|
||||
int requiredPadding = targetWidth - string.codePointCount();
|
||||
|
||||
if (requiredPadding <= 0) {
|
||||
// Padding is not required.
|
||||
return length;
|
||||
}
|
||||
|
||||
length = innerLength;
|
||||
string.copyFrom(backup);
|
||||
if (position == PadPosition.AFTER_PREFIX) {
|
||||
length += addPaddingHelper(paddingString, requiredPadding, string, leftIndex);
|
||||
} else if (position == PadPosition.BEFORE_SUFFIX) {
|
||||
length += addPaddingHelper(paddingString, requiredPadding, string, rightIndex + length);
|
||||
}
|
||||
length += applyMicroMods(micros, string, leftIndex, rightIndex + length);
|
||||
if (position == PadPosition.BEFORE_PREFIX) {
|
||||
length = addPaddingHelper(paddingString, requiredPadding, string, leftIndex);
|
||||
} else if (position == PadPosition.AFTER_SUFFIX) {
|
||||
length = addPaddingHelper(paddingString, requiredPadding, string, rightIndex + length);
|
||||
}
|
||||
|
||||
// The length might not be exactly right due to currency spacing.
|
||||
// Make an adjustment if needed.
|
||||
while (string.codePointCount() < targetWidth) {
|
||||
int insertIndex;
|
||||
switch (position) {
|
||||
case AFTER_PREFIX:
|
||||
insertIndex = leftIndex + length;
|
||||
break;
|
||||
case BEFORE_SUFFIX:
|
||||
insertIndex = rightIndex + length;
|
||||
break;
|
||||
default:
|
||||
// Should not happen since currency spacing is always on the inside.
|
||||
throw new AssertionError();
|
||||
}
|
||||
length += string.insert(insertIndex, paddingString, null);
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
private static int applyMicroMods(
|
||||
MicroProps micros, NumberStringBuilder string, int leftIndex, int rightIndex) {
|
||||
int length = micros.modMiddle.apply(string, leftIndex, rightIndex);
|
||||
length += micros.modOuter.apply(string, leftIndex, rightIndex + length);
|
||||
return length;
|
||||
}
|
||||
|
||||
private static int addPaddingHelper(
|
||||
String paddingString, int requiredPadding, NumberStringBuilder string, int index) {
|
||||
for (int i = 0; i < requiredPadding; i++) {
|
||||
string.insert(index, paddingString, null);
|
||||
}
|
||||
return paddingString.length() * requiredPadding;
|
||||
}
|
||||
}
|
114
icu4j/main/classes/core/src/newapi/impl/PositiveDecimalImpl.java
Normal file
114
icu4j/main/classes/core/src/newapi/impl/PositiveDecimalImpl.java
Normal file
@ -0,0 +1,114 @@
|
||||
// © 2017 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package newapi.impl;
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
|
||||
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.NumberFormat;
|
||||
|
||||
import newapi.NumberFormatter.DecimalMarkDisplay;
|
||||
|
||||
public class PositiveDecimalImpl implements Format.TargetFormat {
|
||||
|
||||
@Override
|
||||
public int target(FormatQuantity input, NumberStringBuilder string, int startIndex) {
|
||||
// FIXME
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param micros
|
||||
* @param fq
|
||||
* @param output
|
||||
* @return
|
||||
*/
|
||||
public static int apply(MicroProps micros, FormatQuantity input, NumberStringBuilder string) {
|
||||
int length = 0;
|
||||
if (input.isInfinite()) {
|
||||
length += string.insert(length, micros.symbols.getInfinity(), NumberFormat.Field.INTEGER);
|
||||
|
||||
} else if (input.isNaN()) {
|
||||
length += string.insert(length, micros.symbols.getNaN(), NumberFormat.Field.INTEGER);
|
||||
|
||||
} else {
|
||||
// Add the integer digits
|
||||
length += addIntegerDigits(micros, input, string);
|
||||
|
||||
// Add the decimal point
|
||||
if (input.getLowerDisplayMagnitude() < 0
|
||||
|| micros.decimal == DecimalMarkDisplay.ALWAYS_SHOWN) {
|
||||
length +=
|
||||
string.insert(
|
||||
length,
|
||||
micros.useCurrency
|
||||
? micros.symbols.getMonetaryDecimalSeparatorString()
|
||||
: micros.symbols.getDecimalSeparatorString(),
|
||||
NumberFormat.Field.DECIMAL_SEPARATOR);
|
||||
}
|
||||
|
||||
// Add the fraction digits
|
||||
length += addFractionDigits(micros, input, string);
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
private static int addIntegerDigits(
|
||||
MicroProps micros, FormatQuantity input, NumberStringBuilder string) {
|
||||
int length = 0;
|
||||
int integerCount = input.getUpperDisplayMagnitude() + 1;
|
||||
for (int i = 0; i < integerCount; i++) {
|
||||
// Add grouping separator
|
||||
if (micros.grouping.groupAtPosition(i, input)) {
|
||||
length +=
|
||||
string.insert(
|
||||
0,
|
||||
micros.useCurrency
|
||||
? micros.symbols.getMonetaryGroupingSeparatorString()
|
||||
: micros.symbols.getGroupingSeparatorString(),
|
||||
NumberFormat.Field.GROUPING_SEPARATOR);
|
||||
}
|
||||
|
||||
// Get and append the next digit value
|
||||
byte nextDigit = input.getDigit(i);
|
||||
if (micros.symbols.getCodePointZero() != -1) {
|
||||
length +=
|
||||
string.insertCodePoint(
|
||||
0, micros.symbols.getCodePointZero() + nextDigit, NumberFormat.Field.INTEGER);
|
||||
} else {
|
||||
length +=
|
||||
string.insert(
|
||||
0, micros.symbols.getDigitStringsLocal()[nextDigit], NumberFormat.Field.INTEGER);
|
||||
}
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
private static int addFractionDigits(
|
||||
MicroProps micros, FormatQuantity input, NumberStringBuilder string) {
|
||||
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);
|
||||
if (micros.symbols.getCodePointZero() != -1) {
|
||||
length +=
|
||||
string.appendCodePoint(
|
||||
micros.symbols.getCodePointZero() + nextDigit, NumberFormat.Field.FRACTION);
|
||||
} else {
|
||||
length +=
|
||||
string.append(
|
||||
micros.symbols.getDigitStringsLocal()[nextDigit], NumberFormat.Field.FRACTION);
|
||||
}
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void export(Properties properties) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
10
icu4j/main/classes/core/src/newapi/impl/QuantityChain.java
Normal file
10
icu4j/main/classes/core/src/newapi/impl/QuantityChain.java
Normal file
@ -0,0 +1,10 @@
|
||||
// © 2017 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package newapi.impl;
|
||||
|
||||
import com.ibm.icu.impl.number.FormatQuantity;
|
||||
|
||||
public interface QuantityChain {
|
||||
QuantityChain chain(QuantityChain parent);
|
||||
MicroProps withQuantity(FormatQuantity quantity);
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
// © 2017 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package newapi.impl;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.ibm.icu.impl.StandardPlural;
|
||||
import com.ibm.icu.impl.number.FormatQuantity;
|
||||
import com.ibm.icu.impl.number.Modifier;
|
||||
import com.ibm.icu.text.PluralRules;
|
||||
|
||||
public class QuantityDependentModOuter implements QuantityChain {
|
||||
final Map<StandardPlural, Modifier> data;
|
||||
final PluralRules rules;
|
||||
/* final */ QuantityChain parent;
|
||||
|
||||
public QuantityDependentModOuter(Map<StandardPlural, Modifier> data, PluralRules rules) {
|
||||
this.data = data;
|
||||
this.rules = rules;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuantityChain chain(QuantityChain parent) {
|
||||
this.parent = parent;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MicroProps withQuantity(FormatQuantity quantity) {
|
||||
MicroProps micros = parent.withQuantity(quantity);
|
||||
// TODO: Avoid the copy here?
|
||||
FormatQuantity copy = quantity.createCopy();
|
||||
micros.rounding.apply(copy);
|
||||
micros.modOuter = data.get(copy.getStandardPlural(rules));
|
||||
return micros;
|
||||
}
|
||||
}
|
445
icu4j/main/classes/core/src/newapi/impl/RoundingImpl.java
Normal file
445
icu4j/main/classes/core/src/newapi/impl/RoundingImpl.java
Normal file
@ -0,0 +1,445 @@
|
||||
// © 2017 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package newapi.impl;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
import com.ibm.icu.impl.number.FormatQuantity;
|
||||
import com.ibm.icu.impl.number.FormatQuantity4;
|
||||
import com.ibm.icu.impl.number.LdmlPatternInfo.PatternParseResult;
|
||||
import com.ibm.icu.impl.number.RoundingUtils;
|
||||
import com.ibm.icu.util.Currency;
|
||||
import com.ibm.icu.util.Currency.CurrencyUsage;
|
||||
|
||||
import newapi.NumberFormatter.CurrencyRounding;
|
||||
import newapi.NumberFormatter.FractionRounding;
|
||||
import newapi.NumberFormatter.IRounding;
|
||||
import newapi.NumberFormatter.Rounding;
|
||||
|
||||
/**
|
||||
* The internal version of {@link Rounding} with additional methods.
|
||||
*
|
||||
* <p>Although it seems as though RoundingImpl should extend Rounding, it actually extends
|
||||
* FractionRounding. This is because instances of FractionRounding are self-contained rounding
|
||||
* instances themselves, and they need to implement RoundingImpl. When ICU adopts Java 8, there will
|
||||
* be more options for the polymorphism, such as multiple inheritance with interfaces having default
|
||||
* methods and static factory methods on interfaces.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public abstract class RoundingImpl extends FractionRounding.Internal implements Cloneable {
|
||||
|
||||
public static RoundingImpl forPattern(PatternParseResult patternInfo) {
|
||||
if (patternInfo.positive.rounding != null) {
|
||||
return RoundingImplIncrement.getInstance(patternInfo.positive.rounding.toBigDecimal());
|
||||
} else if (patternInfo.positive.minimumSignificantDigits > 0) {
|
||||
return RoundingImplSignificant.getInstance(
|
||||
patternInfo.positive.minimumSignificantDigits,
|
||||
patternInfo.positive.maximumSignificantDigits);
|
||||
} else if (patternInfo.positive.exponentDigits > 0) {
|
||||
// FIXME
|
||||
throw new UnsupportedOperationException();
|
||||
} else {
|
||||
return RoundingImplFraction.getInstance(
|
||||
patternInfo.positive.minimumFractionDigits, patternInfo.positive.maximumFractionDigits);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a RoundingImpl no matter what is the type of the provided argument. If the argument is
|
||||
* already a RoundingImpl, this method just returns the same object. Otherwise, it does some
|
||||
* processing to build a RoundingImpl.
|
||||
*
|
||||
* @param rounding The input object, which might or might not be a RoundingImpl.
|
||||
* @param currency A currency object to use in case the input object needs it.
|
||||
* @return A RoundingImpl object.
|
||||
*/
|
||||
public static RoundingImpl normalizeType(IRounding rounding, Currency currency) {
|
||||
if (rounding instanceof RoundingImpl) {
|
||||
return (RoundingImpl) rounding;
|
||||
} else if (rounding instanceof RoundingImplCurrency) {
|
||||
return ((RoundingImplCurrency) rounding).withCurrency(currency);
|
||||
} else {
|
||||
return RoundingImplLambda.getInstance(rounding);
|
||||
}
|
||||
}
|
||||
|
||||
private static final MathContext DEFAULT_MATH_CONTEXT =
|
||||
RoundingUtils.mathContextUnlimited(RoundingMode.HALF_EVEN);
|
||||
|
||||
public MathContext mathContext;
|
||||
|
||||
public RoundingImpl() {
|
||||
this.mathContext = DEFAULT_MATH_CONTEXT;
|
||||
// TODO: This is ugly, but necessary if a RoundingImpl is created
|
||||
// before this class has been initialized.
|
||||
if (this.mathContext == null) {
|
||||
this.mathContext = RoundingUtils.mathContextUnlimited(RoundingMode.HALF_EVEN);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rounding withMode(RoundingMode roundingMode) {
|
||||
return withMode(RoundingUtils.mathContextUnlimited(roundingMode));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rounding withMode(MathContext mathContext) {
|
||||
if (this.mathContext.equals(mathContext)) {
|
||||
return this;
|
||||
}
|
||||
RoundingImpl other = (RoundingImpl) this.clone();
|
||||
other.mathContext = mathContext;
|
||||
return other;
|
||||
}
|
||||
|
||||
abstract void apply(FormatQuantity value);
|
||||
|
||||
@Override
|
||||
public BigDecimal round(BigDecimal input) {
|
||||
// Provided for API compatibility.
|
||||
FormatQuantity fq = new FormatQuantity4(input);
|
||||
this.apply(fq);
|
||||
return fq.toBigDecimal();
|
||||
}
|
||||
|
||||
static interface MultiplierProducer {
|
||||
int getMultiplier(int magnitude);
|
||||
}
|
||||
|
||||
int chooseMultiplierAndApply(FormatQuantity input, MultiplierProducer producer) {
|
||||
// TODO: Make a better and more efficient implementation.
|
||||
// TODO: Avoid the object creation here.
|
||||
FormatQuantity copy = input.createCopy();
|
||||
|
||||
assert !input.isZero();
|
||||
int magnitude = input.getMagnitude();
|
||||
int multiplier = producer.getMultiplier(magnitude);
|
||||
input.adjustMagnitude(multiplier);
|
||||
apply(input);
|
||||
|
||||
// If the number turned to zero when rounding, do not re-attempt the rounding.
|
||||
if (!input.isZero() && input.getMagnitude() == magnitude + multiplier + 1) {
|
||||
magnitude += 1;
|
||||
input.copyFrom(copy);
|
||||
multiplier = producer.getMultiplier(magnitude);
|
||||
input.adjustMagnitude(multiplier);
|
||||
assert input.getMagnitude() == magnitude + multiplier - 1;
|
||||
apply(input);
|
||||
assert input.getMagnitude() == magnitude + multiplier;
|
||||
}
|
||||
|
||||
return multiplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object clone() {
|
||||
try {
|
||||
return super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
// Should not happen since parent is Object
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/** A dummy class used when the number has already been rounded elsewhere. */
|
||||
public static class RoundingImplDummy extends RoundingImpl {
|
||||
public static final RoundingImplDummy INSTANCE = new RoundingImplDummy();
|
||||
|
||||
private RoundingImplDummy() {}
|
||||
|
||||
@Override
|
||||
void apply(FormatQuantity value) {}
|
||||
}
|
||||
|
||||
public static class RoundingImplInfinity extends RoundingImpl {
|
||||
@Override
|
||||
void apply(FormatQuantity value) {
|
||||
value.roundToInfinity();
|
||||
value.setFractionLength(0, Integer.MAX_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
public static class RoundingImplFraction extends RoundingImpl {
|
||||
int minFrac;
|
||||
int maxFrac;
|
||||
|
||||
private static final RoundingImplFraction FIXED_0 = new RoundingImplFraction(0, 0);
|
||||
private static final RoundingImplFraction FIXED_2 = new RoundingImplFraction(2, 2);
|
||||
|
||||
/** Assumes that minFrac <= maxFrac. */
|
||||
public static RoundingImplFraction getInstance(int minFrac, int maxFrac) {
|
||||
assert minFrac >= 0 && minFrac <= maxFrac;
|
||||
if (minFrac == 0 && maxFrac == 0) {
|
||||
return FIXED_0;
|
||||
} else if (minFrac == 2 && maxFrac == 2) {
|
||||
return FIXED_2;
|
||||
} else {
|
||||
return new RoundingImplFraction(minFrac, maxFrac);
|
||||
}
|
||||
}
|
||||
|
||||
/** Hook for public static final; uses integer rounding */
|
||||
public RoundingImplFraction() {
|
||||
this(0, 0);
|
||||
}
|
||||
|
||||
private RoundingImplFraction(int minFrac, int maxFrac) {
|
||||
this.minFrac = minFrac;
|
||||
this.maxFrac = maxFrac;
|
||||
}
|
||||
|
||||
@Override
|
||||
void apply(FormatQuantity value) {
|
||||
value.roundToMagnitude(getRoundingMagnitude(maxFrac), mathContext);
|
||||
value.setFractionLength(Math.max(0, -getDisplayMagnitude(minFrac)), Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
static int getRoundingMagnitude(int maxFrac) {
|
||||
if (maxFrac == Integer.MAX_VALUE) {
|
||||
return Integer.MIN_VALUE;
|
||||
}
|
||||
return -maxFrac;
|
||||
}
|
||||
|
||||
static int getDisplayMagnitude(int minFrac) {
|
||||
if (minFrac == 0) {
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
return -minFrac;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rounding withMinFigures(int minFigures) {
|
||||
if (minFigures > 0 && minFigures <= MAX_VALUE) {
|
||||
return RoundingImplFractionSignificant.getInstance(this, minFigures, -1);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Significant digits must be between 0 and " + MAX_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rounding withMaxFigures(int maxFigures) {
|
||||
if (maxFigures > 0 && maxFigures <= MAX_VALUE) {
|
||||
return RoundingImplFractionSignificant.getInstance(this, -1, maxFigures);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Significant digits must be between 0 and " + MAX_VALUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class RoundingImplSignificant extends RoundingImpl {
|
||||
int minSig;
|
||||
int maxSig;
|
||||
|
||||
private static final RoundingImplSignificant FIXED_2 = new RoundingImplSignificant(2, 2);
|
||||
private static final RoundingImplSignificant FIXED_3 = new RoundingImplSignificant(3, 3);
|
||||
private static final RoundingImplSignificant RANGE_2_3 = new RoundingImplSignificant(2, 3);
|
||||
|
||||
/** Assumes that minSig <= maxSig. */
|
||||
public static RoundingImplSignificant getInstance(int minSig, int maxSig) {
|
||||
assert minSig >= 0 && minSig <= maxSig;
|
||||
if (minSig == 2 && maxSig == 2) {
|
||||
return FIXED_2;
|
||||
} else if (minSig == 3 && maxSig == 3) {
|
||||
return FIXED_3;
|
||||
} else if (minSig == 2 && maxSig == 3) {
|
||||
return RANGE_2_3;
|
||||
} else {
|
||||
return new RoundingImplSignificant(minSig, maxSig);
|
||||
}
|
||||
}
|
||||
|
||||
RoundingImplSignificant(int minSig, int maxSig) {
|
||||
this.minSig = minSig;
|
||||
this.maxSig = maxSig;
|
||||
}
|
||||
|
||||
@Override
|
||||
void apply(FormatQuantity value) {
|
||||
value.roundToMagnitude(getRoundingMagnitude(value, maxSig), mathContext);
|
||||
value.setFractionLength(Math.max(0, -getDisplayMagnitude(value, minSig)), Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
/** Version of {@link #apply} that obeys minInt constraints. */
|
||||
public void apply(FormatQuantity quantity, int minInt) {
|
||||
assert quantity.isZero();
|
||||
quantity.setFractionLength(minSig - minInt, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
static int getRoundingMagnitude(FormatQuantity value, int maxSig) {
|
||||
if (maxSig == Integer.MAX_VALUE) {
|
||||
return Integer.MIN_VALUE;
|
||||
}
|
||||
int magnitude = value.isZero() ? 0 : value.getMagnitude();
|
||||
return magnitude - maxSig + 1;
|
||||
}
|
||||
|
||||
static int getDisplayMagnitude(FormatQuantity value, int minSig) {
|
||||
int magnitude = value.isZero() ? 0 : value.getMagnitude();
|
||||
return magnitude - minSig + 1;
|
||||
}
|
||||
}
|
||||
|
||||
public static class RoundingImplFractionSignificant extends RoundingImpl {
|
||||
int minFrac;
|
||||
int maxFrac;
|
||||
int minSig;
|
||||
int maxSig;
|
||||
|
||||
// Package-private
|
||||
static final RoundingImplFractionSignificant COMPACT_STRATEGY =
|
||||
new RoundingImplFractionSignificant(0, 0, 2, -1);
|
||||
|
||||
public static Rounding getInstance(FractionRounding _base, int minSig, int maxSig) {
|
||||
assert _base instanceof RoundingImplFraction;
|
||||
RoundingImplFraction base = (RoundingImplFraction) _base;
|
||||
if (base.minFrac == 0 && base.maxFrac == 0 && minSig == 2 /* && maxSig == -1 */) {
|
||||
return COMPACT_STRATEGY;
|
||||
} else {
|
||||
return new RoundingImplFractionSignificant(base.minFrac, base.maxFrac, minSig, maxSig);
|
||||
}
|
||||
}
|
||||
|
||||
/** Assumes that minFrac <= maxFrac and minSig <= maxSig except for -1. */
|
||||
private RoundingImplFractionSignificant(int minFrac, int maxFrac, int minSig, int maxSig) {
|
||||
// Exactly one of the arguments should be -1, either minSig or maxSig.
|
||||
assert (minFrac != -1 && maxFrac != -1 && minSig == -1 && maxSig != -1 && minFrac <= maxFrac)
|
||||
|| (minFrac != -1 && maxFrac != -1 && minSig != -1 && maxSig == -1 && minFrac <= maxFrac);
|
||||
this.minFrac = minFrac;
|
||||
this.maxFrac = maxFrac;
|
||||
this.minSig = minSig;
|
||||
this.maxSig = maxSig;
|
||||
}
|
||||
|
||||
@Override
|
||||
void apply(FormatQuantity value) {
|
||||
int displayMag = RoundingImplFraction.getDisplayMagnitude(minFrac);
|
||||
int roundingMag = RoundingImplFraction.getRoundingMagnitude(maxFrac);
|
||||
if (minSig == -1) {
|
||||
// Max Sig override
|
||||
int candidate = RoundingImplSignificant.getRoundingMagnitude(value, maxSig);
|
||||
roundingMag = Math.max(roundingMag, candidate);
|
||||
} else {
|
||||
// Min Sig override
|
||||
int candidate = RoundingImplSignificant.getDisplayMagnitude(value, minSig);
|
||||
roundingMag = Math.min(roundingMag, candidate);
|
||||
}
|
||||
value.roundToMagnitude(roundingMag, mathContext);
|
||||
value.setFractionLength(Math.max(0, -displayMag), Integer.MAX_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
public static class RoundingImplIncrement extends RoundingImpl {
|
||||
BigDecimal increment;
|
||||
|
||||
private static final RoundingImplIncrement NICKEL =
|
||||
new RoundingImplIncrement(BigDecimal.valueOf(0.5));
|
||||
|
||||
public static RoundingImplIncrement getInstance(BigDecimal increment) {
|
||||
assert increment != null;
|
||||
if (increment.compareTo(NICKEL.increment) == 0) {
|
||||
return NICKEL;
|
||||
} else {
|
||||
return new RoundingImplIncrement(increment);
|
||||
}
|
||||
}
|
||||
|
||||
private RoundingImplIncrement(BigDecimal increment) {
|
||||
this.increment = increment;
|
||||
}
|
||||
|
||||
@Override
|
||||
void apply(FormatQuantity value) {
|
||||
value.roundToIncrement(increment, mathContext);
|
||||
value.setFractionLength(increment.scale(), increment.scale());
|
||||
}
|
||||
}
|
||||
|
||||
public static class RoundingImplLambda extends RoundingImpl {
|
||||
IRounding lambda;
|
||||
|
||||
public static RoundingImplLambda getInstance(IRounding lambda) {
|
||||
assert !(lambda instanceof Rounding);
|
||||
return new RoundingImplLambda(lambda);
|
||||
}
|
||||
|
||||
private RoundingImplLambda(IRounding lambda) {
|
||||
this.lambda = lambda;
|
||||
}
|
||||
|
||||
@Override
|
||||
void apply(FormatQuantity value) {
|
||||
// TODO: Cache the BigDecimal between calls?
|
||||
BigDecimal temp = value.toBigDecimal();
|
||||
temp = lambda.round(temp);
|
||||
value.setToBigDecimal(temp);
|
||||
value.setFractionLength(temp.scale(), Integer.MAX_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: This is unlike the other classes here. It is NOT a standalone rounder and it does NOT
|
||||
* extend RoundingImpl.
|
||||
*/
|
||||
public static class RoundingImplCurrency extends CurrencyRounding.Internal {
|
||||
final CurrencyUsage usage;
|
||||
final MathContext mc;
|
||||
|
||||
private static final RoundingImplCurrency MONETARY_STANDARD =
|
||||
new RoundingImplCurrency(CurrencyUsage.STANDARD, DEFAULT_MATH_CONTEXT);
|
||||
|
||||
private static final RoundingImplCurrency MONETARY_CASH =
|
||||
new RoundingImplCurrency(CurrencyUsage.CASH, DEFAULT_MATH_CONTEXT);
|
||||
|
||||
public static RoundingImplCurrency getInstance(CurrencyUsage usage) {
|
||||
if (usage == CurrencyUsage.STANDARD) {
|
||||
return MONETARY_STANDARD;
|
||||
} else if (usage == CurrencyUsage.CASH) {
|
||||
return MONETARY_CASH;
|
||||
} else {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
private RoundingImplCurrency(CurrencyUsage usage, MathContext mc) {
|
||||
this.usage = usage;
|
||||
this.mc = mc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoundingImpl withCurrency(Currency currency) {
|
||||
assert currency != null;
|
||||
double incrementDouble = currency.getRoundingIncrement(usage);
|
||||
if (incrementDouble != 0.0) {
|
||||
BigDecimal increment = BigDecimal.valueOf(incrementDouble);
|
||||
return RoundingImplIncrement.getInstance(increment);
|
||||
} else {
|
||||
int minMaxFrac = currency.getDefaultFractionDigits(usage);
|
||||
return RoundingImplFraction.getInstance(minMaxFrac, minMaxFrac);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoundingImplCurrency withMode(RoundingMode roundingMode) {
|
||||
// This is similar to RoundingImpl#withMode().
|
||||
return withMode(RoundingUtils.mathContextUnlimited(roundingMode));
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoundingImplCurrency withMode(MathContext mathContext) {
|
||||
// This is similar to RoundingImpl#withMode().
|
||||
if (mc.equals(mathContext)) {
|
||||
return this;
|
||||
}
|
||||
return new RoundingImplCurrency(usage, mathContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal round(BigDecimal input) {
|
||||
throw new UnsupportedOperationException(
|
||||
"A currency must be specified before calling this method.");
|
||||
}
|
||||
}
|
||||
}
|
144
icu4j/main/classes/core/src/newapi/impl/ScientificImpl.java
Normal file
144
icu4j/main/classes/core/src/newapi/impl/ScientificImpl.java
Normal file
@ -0,0 +1,144 @@
|
||||
// © 2017 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package newapi.impl;
|
||||
|
||||
import com.ibm.icu.impl.number.FormatQuantity;
|
||||
import com.ibm.icu.impl.number.Modifier;
|
||||
import com.ibm.icu.impl.number.NumberStringBuilder;
|
||||
import com.ibm.icu.text.DecimalFormatSymbols;
|
||||
import com.ibm.icu.text.NumberFormat;
|
||||
|
||||
import newapi.NumberFormatter.SignDisplay;
|
||||
import newapi.impl.RoundingImpl.RoundingImplDummy;
|
||||
import newapi.impl.RoundingImpl.RoundingImplSignificant;
|
||||
|
||||
public class ScientificImpl implements QuantityChain, RoundingImpl.MultiplierProducer {
|
||||
|
||||
final NotationImpl.NotationScientificImpl notation;
|
||||
final DecimalFormatSymbols symbols;
|
||||
final ScientificModifier[] precomputedMods;
|
||||
/* final */ QuantityChain parent;
|
||||
|
||||
public static ScientificImpl getInstance(
|
||||
NotationImpl.NotationScientificImpl notation, DecimalFormatSymbols symbols, boolean build) {
|
||||
return new ScientificImpl(notation, symbols, build);
|
||||
}
|
||||
|
||||
private ScientificImpl(
|
||||
NotationImpl.NotationScientificImpl notation, DecimalFormatSymbols symbols, boolean build) {
|
||||
this.notation = notation;
|
||||
this.symbols = symbols;
|
||||
|
||||
if (build) {
|
||||
// Pre-build the modifiers for exponents -12 through 12
|
||||
precomputedMods = new ScientificModifier[25];
|
||||
for (int i = -12; i <= 12; i++) {
|
||||
precomputedMods[i + 12] = new ScientificModifier(i);
|
||||
}
|
||||
} else {
|
||||
precomputedMods = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuantityChain chain(QuantityChain parent) {
|
||||
this.parent = parent;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MicroProps withQuantity(FormatQuantity quantity) {
|
||||
MicroProps micros = parent.withQuantity(quantity);
|
||||
assert micros.rounding != null;
|
||||
|
||||
// Treat zero as if it had magnitude 0
|
||||
int exponent;
|
||||
if (quantity.isZero()) {
|
||||
if (notation.requireMinInt && micros.rounding instanceof RoundingImplSignificant) {
|
||||
// Shown "00.000E0" on pattern "00.000E0"
|
||||
((RoundingImplSignificant) micros.rounding).apply(quantity, notation.engineeringInterval);
|
||||
exponent = 0;
|
||||
} else {
|
||||
micros.rounding.apply(quantity);
|
||||
exponent = 0;
|
||||
}
|
||||
} else {
|
||||
exponent = -micros.rounding.chooseMultiplierAndApply(quantity, this);
|
||||
}
|
||||
|
||||
// Add the Modifier for the scientific format.
|
||||
if (precomputedMods != null && exponent >= -12 && exponent <= 12) {
|
||||
micros.modInner = precomputedMods[exponent + 12];
|
||||
} else {
|
||||
micros.modInner = new ScientificModifier(exponent);
|
||||
}
|
||||
|
||||
// We already performed rounding. Do not perform it again.
|
||||
micros.rounding = RoundingImplDummy.INSTANCE;
|
||||
|
||||
return micros;
|
||||
}
|
||||
|
||||
private class ScientificModifier implements Modifier {
|
||||
final int exponent;
|
||||
|
||||
ScientificModifier(int exponent) {
|
||||
this.exponent = exponent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int apply(NumberStringBuilder output, int leftIndex, int rightIndex) {
|
||||
// FIXME: Localized exponent separator location.
|
||||
int i = rightIndex;
|
||||
// Append the exponent separator and sign
|
||||
i += output.insert(i, symbols.getExponentSeparator(), NumberFormat.Field.EXPONENT_SYMBOL);
|
||||
if (exponent < 0 && notation.exponentSignDisplay != SignDisplay.NEVER_SHOWN) {
|
||||
i += output.insert(i, symbols.getMinusSignString(), NumberFormat.Field.EXPONENT_SIGN);
|
||||
} else if (notation.exponentSignDisplay == SignDisplay.ALWAYS_SHOWN) {
|
||||
i += output.insert(i, symbols.getPlusSignString(), NumberFormat.Field.EXPONENT_SIGN);
|
||||
}
|
||||
// Append the exponent digits (using a simple inline algorithm)
|
||||
int disp = Math.abs(exponent);
|
||||
for (int j = 0; j < notation.minExponentDigits || disp > 0; j++, disp /= 10) {
|
||||
int d = disp % 10;
|
||||
String digitString = symbols.getDigitStringsLocal()[d];
|
||||
i += output.insert(i - j, digitString, NumberFormat.Field.EXPONENT);
|
||||
}
|
||||
return i - rightIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStrong() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPrefix() {
|
||||
// Should never get called
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSuffix() {
|
||||
// Should never get called
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMultiplier(int magnitude) {
|
||||
int interval = notation.engineeringInterval;
|
||||
int digitsShown;
|
||||
if (notation.requireMinInt) {
|
||||
// For patterns like "000.00E0" and ".00E0"
|
||||
digitsShown = interval;
|
||||
} else if (interval <= 1) {
|
||||
// For patterns like "0.00E0" and "@@@E0"
|
||||
digitsShown = 1;
|
||||
} else {
|
||||
// For patterns like "##0.00"
|
||||
digitsShown = ((magnitude % interval + interval) % interval) + 1;
|
||||
}
|
||||
return digitsShown - magnitude - 1;
|
||||
}
|
||||
}
|
549
icu4j/main/classes/core/src/newapi/impl/SkeletonBuilder.java
Normal file
549
icu4j/main/classes/core/src/newapi/impl/SkeletonBuilder.java
Normal file
@ -0,0 +1,549 @@
|
||||
// © 2017 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package newapi.impl;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
|
||||
import com.ibm.icu.text.DecimalFormatSymbols;
|
||||
import com.ibm.icu.text.MeasureFormat.FormatWidth;
|
||||
import com.ibm.icu.text.NumberingSystem;
|
||||
import com.ibm.icu.util.Currency;
|
||||
import com.ibm.icu.util.Currency.CurrencyUsage;
|
||||
import com.ibm.icu.util.Dimensionless;
|
||||
import com.ibm.icu.util.MeasureUnit;
|
||||
|
||||
import newapi.NumberFormatter.DecimalMarkDisplay;
|
||||
import newapi.NumberFormatter.FractionRounding;
|
||||
import newapi.NumberFormatter.Grouping;
|
||||
import newapi.NumberFormatter.IGrouping;
|
||||
import newapi.NumberFormatter.IRounding;
|
||||
import newapi.NumberFormatter.IntegerWidth;
|
||||
import newapi.NumberFormatter.Notation;
|
||||
import newapi.NumberFormatter.NotationCompact;
|
||||
import newapi.NumberFormatter.NotationScientific;
|
||||
import newapi.NumberFormatter.NotationSimple;
|
||||
import newapi.NumberFormatter.Padding;
|
||||
import newapi.NumberFormatter.Rounding;
|
||||
import newapi.NumberFormatter.SignDisplay;
|
||||
import newapi.impl.RoundingImpl.RoundingImplCurrency;
|
||||
import newapi.impl.RoundingImpl.RoundingImplFraction;
|
||||
import newapi.impl.RoundingImpl.RoundingImplFractionSignificant;
|
||||
import newapi.impl.RoundingImpl.RoundingImplIncrement;
|
||||
import newapi.impl.RoundingImpl.RoundingImplInfinity;
|
||||
import newapi.impl.RoundingImpl.RoundingImplSignificant;
|
||||
|
||||
public final class SkeletonBuilder {
|
||||
|
||||
public static String macrosToSkeleton(MacroProps macros) {
|
||||
// Print out the values in their canonical order.
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (macros.notation != null) {
|
||||
// sb.append("notation=");
|
||||
notationToSkeleton(macros.notation, sb);
|
||||
sb.append(' ');
|
||||
}
|
||||
if (macros.unit != null) {
|
||||
// sb.append("unit=");
|
||||
unitToSkeleton(macros.unit, sb);
|
||||
sb.append(' ');
|
||||
}
|
||||
if (macros.rounding != null) {
|
||||
// sb.append("rounding=");
|
||||
roundingToSkeleton(macros.rounding, sb);
|
||||
sb.append(' ');
|
||||
}
|
||||
if (macros.grouping != null) {
|
||||
sb.append("grouping=");
|
||||
groupingToSkeleton(macros.grouping, sb);
|
||||
sb.append(' ');
|
||||
}
|
||||
if (macros.padding != null) {
|
||||
sb.append("padding=");
|
||||
paddingToSkeleton(macros.padding, sb);
|
||||
sb.append(' ');
|
||||
}
|
||||
if (macros.integerWidth != null) {
|
||||
sb.append("integer-width=");
|
||||
integerWidthToSkeleton(macros.integerWidth, sb);
|
||||
sb.append(' ');
|
||||
}
|
||||
if (macros.symbols != null) {
|
||||
sb.append("symbols=");
|
||||
symbolsToSkeleton(macros.symbols, sb);
|
||||
sb.append(' ');
|
||||
}
|
||||
if (macros.unitWidth != null) {
|
||||
sb.append("unit-width=");
|
||||
unitWidthToSkeleton(macros.unitWidth, sb);
|
||||
sb.append(' ');
|
||||
}
|
||||
if (macros.sign != null) {
|
||||
sb.append("sign=");
|
||||
signToSkeleton(macros.sign, sb);
|
||||
sb.append(' ');
|
||||
}
|
||||
if (macros.decimal != null) {
|
||||
sb.append("decimal=");
|
||||
decimalToSkeleton(macros.decimal, sb);
|
||||
sb.append(' ');
|
||||
}
|
||||
if (sb.length() > 0) {
|
||||
// Remove the trailing space
|
||||
sb.setLength(sb.length() - 1);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static MacroProps skeletonToMacros(String skeleton) {
|
||||
MacroProps macros = new MacroProps();
|
||||
for (int offset = 0; offset < skeleton.length(); ) {
|
||||
char c = skeleton.charAt(offset);
|
||||
switch (c) {
|
||||
case ' ':
|
||||
offset++;
|
||||
break;
|
||||
case 'E':
|
||||
case 'C':
|
||||
case 'I':
|
||||
offset += skeletonToNotation(skeleton, offset, macros);
|
||||
break;
|
||||
case '%':
|
||||
case 'B':
|
||||
case '$':
|
||||
case 'U':
|
||||
offset += skeletonToUnit(skeleton, offset, macros);
|
||||
break;
|
||||
case 'F':
|
||||
case 'S':
|
||||
case 'M':
|
||||
case 'G':
|
||||
case 'Y':
|
||||
offset += skeletonToRounding(skeleton, offset, macros);
|
||||
break;
|
||||
default:
|
||||
if (skeleton.regionMatches(offset, "notation=", 0, 9)) {
|
||||
offset += 9;
|
||||
offset += skeletonToNotation(skeleton, offset, macros);
|
||||
} else if (skeleton.regionMatches(offset, "unit=", 0, 5)) {
|
||||
offset += 5;
|
||||
offset += skeletonToUnit(skeleton, offset, macros);
|
||||
} else if (skeleton.regionMatches(offset, "rounding=", 0, 9)) {
|
||||
offset += 9;
|
||||
offset += skeletonToRounding(skeleton, offset, macros);
|
||||
} else if (skeleton.regionMatches(offset, "grouping=", 0, 9)) {
|
||||
offset += 9;
|
||||
offset += skeletonToGrouping(skeleton, offset, macros);
|
||||
} else if (skeleton.regionMatches(offset, "padding=", 0, 9)) {
|
||||
offset += 8;
|
||||
offset += skeletonToPadding(skeleton, offset, macros);
|
||||
} else if (skeleton.regionMatches(offset, "integer-width=", 0, 9)) {
|
||||
offset += 14;
|
||||
offset += skeletonToIntegerWidth(skeleton, offset, macros);
|
||||
} else if (skeleton.regionMatches(offset, "symbols=", 0, 9)) {
|
||||
offset += 8;
|
||||
offset += skeletonToSymbols(skeleton, offset, macros);
|
||||
} else if (skeleton.regionMatches(offset, "unit-width=", 0, 9)) {
|
||||
offset += 11;
|
||||
offset += skeletonToUnitWidth(skeleton, offset, macros);
|
||||
} else if (skeleton.regionMatches(offset, "sign=", 0, 9)) {
|
||||
offset += 5;
|
||||
offset += skeletonToSign(skeleton, offset, macros);
|
||||
} else if (skeleton.regionMatches(offset, "decimal=", 0, 9)) {
|
||||
offset += 8;
|
||||
offset += skeletonToDecimal(skeleton, offset, macros);
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Unexpected token at offset " + offset + " in skeleton string: " + c);
|
||||
}
|
||||
}
|
||||
}
|
||||
return macros;
|
||||
}
|
||||
|
||||
private static void notationToSkeleton(Notation value, StringBuilder sb) {
|
||||
if (value instanceof NotationScientific) {
|
||||
NotationImpl.NotationScientificImpl notation = (NotationImpl.NotationScientificImpl) value;
|
||||
sb.append('E');
|
||||
if (notation.engineeringInterval != 1) {
|
||||
sb.append(notation.engineeringInterval);
|
||||
}
|
||||
if (notation.exponentSignDisplay == SignDisplay.ALWAYS_SHOWN) {
|
||||
sb.append('+');
|
||||
} else if (notation.exponentSignDisplay == SignDisplay.NEVER_SHOWN) {
|
||||
sb.append('!');
|
||||
} else {
|
||||
assert notation.exponentSignDisplay == SignDisplay.AUTO;
|
||||
}
|
||||
if (notation.minExponentDigits != 1) {
|
||||
for (int i = 0; i < notation.minExponentDigits; i++) {
|
||||
sb.append('0');
|
||||
}
|
||||
}
|
||||
} else if (value instanceof NotationCompact) {
|
||||
NotationImpl.NotationCompactImpl notation = (NotationImpl.NotationCompactImpl) value;
|
||||
if (notation.compactStyle == CompactStyle.SHORT) {
|
||||
sb.append('C');
|
||||
} else {
|
||||
// FIXME: CCC or CCCC instead?
|
||||
sb.append("CC");
|
||||
}
|
||||
} else {
|
||||
assert value instanceof NotationSimple;
|
||||
sb.append('I');
|
||||
}
|
||||
}
|
||||
|
||||
private static int skeletonToNotation(String skeleton, int offset, MacroProps output) {
|
||||
int originalOffset = offset;
|
||||
char c0 = skeleton.charAt(offset++);
|
||||
Notation result = null;
|
||||
if (c0 == 'E') {
|
||||
int engineering = 1;
|
||||
SignDisplay sign = SignDisplay.AUTO;
|
||||
int minExponentDigits = 0;
|
||||
char c = safeCharAt(skeleton, offset++);
|
||||
if (c >= '1' && c <= '9') {
|
||||
engineering = c - '0';
|
||||
c = safeCharAt(skeleton, offset++);
|
||||
}
|
||||
if (c == '+') {
|
||||
sign = SignDisplay.ALWAYS_SHOWN;
|
||||
c = safeCharAt(skeleton, offset++);
|
||||
}
|
||||
if (c == '!') {
|
||||
sign = SignDisplay.NEVER_SHOWN;
|
||||
c = safeCharAt(skeleton, offset++);
|
||||
}
|
||||
while (c == '0') {
|
||||
minExponentDigits++;
|
||||
c = safeCharAt(skeleton, offset++);
|
||||
}
|
||||
minExponentDigits = Math.max(1, minExponentDigits);
|
||||
result = new NotationImpl.NotationScientificImpl(engineering, false, minExponentDigits, sign);
|
||||
} else if (c0 == 'C') {
|
||||
char c = safeCharAt(skeleton, offset++);
|
||||
if (c == 'C') {
|
||||
result = Notation.COMPACT_LONG;
|
||||
} else {
|
||||
result = Notation.COMPACT_SHORT;
|
||||
}
|
||||
} else if (c0 == 'I') {
|
||||
result = Notation.SIMPLE;
|
||||
}
|
||||
output.notation = result;
|
||||
return offset - originalOffset;
|
||||
}
|
||||
|
||||
private static void unitToSkeleton(MeasureUnit value, StringBuilder sb) {
|
||||
if (value.getType().equals("dimensionless")) {
|
||||
if (value.getSubtype().equals("percent")) {
|
||||
sb.append('%');
|
||||
} else if (value.getSubtype().equals("permille")) {
|
||||
sb.append("%%");
|
||||
} else {
|
||||
assert value.getSubtype().equals("base");
|
||||
sb.append('B');
|
||||
}
|
||||
} else if (value.getType().equals("currency")) {
|
||||
sb.append('$');
|
||||
sb.append(value.getSubtype());
|
||||
} else {
|
||||
sb.append('U');
|
||||
sb.append(value.getType());
|
||||
sb.append(':');
|
||||
sb.append(value.getSubtype());
|
||||
}
|
||||
}
|
||||
|
||||
private static int skeletonToUnit(String skeleton, int offset, MacroProps output) {
|
||||
int originalOffset = offset;
|
||||
char c0 = skeleton.charAt(offset++);
|
||||
MeasureUnit result = null;
|
||||
if (c0 == '%') {
|
||||
char c = safeCharAt(skeleton, offset++);
|
||||
if (c == '%') {
|
||||
result = Dimensionless.PERCENT;
|
||||
} else {
|
||||
result = Dimensionless.PERMILLE;
|
||||
}
|
||||
} else if (c0 == 'B') {
|
||||
result = Dimensionless.BASE;
|
||||
} else if (c0 == '$') {
|
||||
String currencyCode = skeleton.substring(offset, offset + 3);
|
||||
offset += 3;
|
||||
result = Currency.getInstance(currencyCode);
|
||||
} else if (c0 == 'U') {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
offset += consumeUntil(skeleton, offset, ':', sb);
|
||||
String type = sb.toString();
|
||||
sb.setLength(0);
|
||||
offset += consumeUntil(skeleton, offset, ' ', sb);
|
||||
String subtype = sb.toString();
|
||||
for (MeasureUnit candidate : MeasureUnit.getAvailable(type)) {
|
||||
if (candidate.getSubtype().equals(subtype)) {
|
||||
result = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
output.unit = result;
|
||||
return offset - originalOffset;
|
||||
}
|
||||
|
||||
private static void roundingToSkeleton(IRounding value, StringBuilder sb) {
|
||||
if (!(value instanceof Rounding)) {
|
||||
// FIXME: Throw an exception here instead?
|
||||
return;
|
||||
}
|
||||
MathContext mathContext;
|
||||
if (value instanceof RoundingImplFraction) {
|
||||
RoundingImplFraction rounding = (RoundingImplFraction) value;
|
||||
sb.append('F');
|
||||
minMaxToSkeletonHelper(rounding.minFrac, rounding.maxFrac, sb);
|
||||
mathContext = rounding.mathContext;
|
||||
} else if (value instanceof RoundingImplSignificant) {
|
||||
RoundingImplSignificant rounding = (RoundingImplSignificant) value;
|
||||
sb.append('S');
|
||||
minMaxToSkeletonHelper(rounding.minSig, rounding.maxSig, sb);
|
||||
mathContext = rounding.mathContext;
|
||||
} else if (value instanceof RoundingImplFractionSignificant) {
|
||||
RoundingImplFractionSignificant rounding = (RoundingImplFractionSignificant) value;
|
||||
sb.append('F');
|
||||
minMaxToSkeletonHelper(rounding.minFrac, rounding.maxFrac, sb);
|
||||
if (rounding.minSig != -1) {
|
||||
sb.append('>');
|
||||
sb.append(rounding.minSig);
|
||||
} else {
|
||||
sb.append('<');
|
||||
sb.append(rounding.maxSig);
|
||||
}
|
||||
mathContext = rounding.mathContext;
|
||||
} else if (value instanceof RoundingImplIncrement) {
|
||||
RoundingImplIncrement rounding = (RoundingImplIncrement) value;
|
||||
sb.append('M');
|
||||
sb.append(rounding.increment.toString());
|
||||
mathContext = rounding.mathContext;
|
||||
} else if (value instanceof RoundingImplCurrency) {
|
||||
RoundingImplCurrency rounding = (RoundingImplCurrency) value;
|
||||
sb.append('G');
|
||||
sb.append(rounding.usage.name());
|
||||
mathContext = rounding.mc;
|
||||
} else {
|
||||
RoundingImplInfinity rounding = (RoundingImplInfinity) value;
|
||||
sb.append('Y');
|
||||
mathContext = rounding.mathContext;
|
||||
}
|
||||
// RoundingMode
|
||||
RoundingMode roundingMode = mathContext.getRoundingMode();
|
||||
if (roundingMode != RoundingMode.HALF_EVEN) {
|
||||
sb.append(';');
|
||||
sb.append(roundingMode.name());
|
||||
}
|
||||
}
|
||||
|
||||
private static void minMaxToSkeletonHelper(int minFrac, int maxFrac, StringBuilder sb) {
|
||||
if (minFrac == maxFrac) {
|
||||
sb.append(minFrac);
|
||||
} else {
|
||||
boolean showMaxFrac = (maxFrac >= 0 && maxFrac < Integer.MAX_VALUE);
|
||||
if (minFrac > 0 || !showMaxFrac) {
|
||||
sb.append(minFrac);
|
||||
}
|
||||
sb.append('-');
|
||||
if (showMaxFrac) {
|
||||
sb.append(maxFrac);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int skeletonToRounding(String skeleton, int offset, MacroProps output) {
|
||||
int originalOffset = offset;
|
||||
char c0 = skeleton.charAt(offset++);
|
||||
Rounding result = null;
|
||||
if (c0 == 'F') {
|
||||
int[] minMax = new int[2];
|
||||
offset += skeletonToMinMaxHelper(skeleton, offset, minMax);
|
||||
FractionRounding temp = RoundingImplFraction.getInstance(minMax[0], minMax[1]);
|
||||
char c1 = skeleton.charAt(offset++);
|
||||
if (c1 == '<') {
|
||||
char c2 = skeleton.charAt(offset++);
|
||||
result = temp.withMaxFigures(c2 - '0');
|
||||
} else if (c1 == '>') {
|
||||
char c2 = skeleton.charAt(offset++);
|
||||
result = temp.withMinFigures(c2 - '0');
|
||||
} else {
|
||||
result = temp;
|
||||
}
|
||||
} else if (c0 == 'S') {
|
||||
int[] minMax = new int[2];
|
||||
offset += skeletonToMinMaxHelper(skeleton, offset, minMax);
|
||||
result = RoundingImplSignificant.getInstance(minMax[0], minMax[1]);
|
||||
} else if (c0 == 'M') {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
offset += consumeUntil(skeleton, offset, ' ', sb);
|
||||
BigDecimal increment = new BigDecimal(sb.toString());
|
||||
result = RoundingImplIncrement.getInstance(increment);
|
||||
} else if (c0 == 'G') {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
offset += consumeUntil(skeleton, offset, ' ', sb);
|
||||
CurrencyUsage usage = Enum.valueOf(CurrencyUsage.class, sb.toString());
|
||||
result = Rounding.currency(usage);
|
||||
} else if (c0 == 'Y') {
|
||||
result = Rounding.NONE;
|
||||
}
|
||||
output.rounding = result;
|
||||
return offset - originalOffset;
|
||||
}
|
||||
|
||||
private static int skeletonToMinMaxHelper(String skeleton, int offset, int[] output) {
|
||||
int originalOffset = offset;
|
||||
char c0 = safeCharAt(skeleton, offset++);
|
||||
char c1 = safeCharAt(skeleton, offset++);
|
||||
// TODO: This algorithm breaks if the number is more than 1 char wide.
|
||||
if (c1 == '-') {
|
||||
output[0] = c0 - '0';
|
||||
char c2 = safeCharAt(skeleton, offset++);
|
||||
if (c2 == ' ') {
|
||||
output[1] = Integer.MAX_VALUE;
|
||||
} else {
|
||||
output[1] = c2 - '0';
|
||||
}
|
||||
} else if ('0' <= c1 && c1 <= '9') {
|
||||
output[0] = 0;
|
||||
output[1] = c1 - '0';
|
||||
} else {
|
||||
offset--;
|
||||
output[0] = c0 - '0';
|
||||
output[1] = c0 - '0';
|
||||
}
|
||||
return offset - originalOffset;
|
||||
}
|
||||
|
||||
private static void groupingToSkeleton(IGrouping value, StringBuilder sb) {
|
||||
if (!(value instanceof Grouping)) {
|
||||
// FIXME: Throw an exception here instead?
|
||||
sb.append("custom");
|
||||
return;
|
||||
}
|
||||
if (value.equals(Grouping.DEFAULT)) {
|
||||
sb.append("DEFAULT");
|
||||
} else if (value.equals(Grouping.DEFAULT_MIN_2_DIGITS)) {
|
||||
sb.append("DEFAULT_MIN_2_DIGITS");
|
||||
} else if (value.equals(Grouping.NONE)) {
|
||||
sb.append("NONE");
|
||||
} else {
|
||||
GroupingImpl grouping = (GroupingImpl) value;
|
||||
if (grouping.grouping2 == -1 || grouping.grouping2 == 0) {
|
||||
sb.append("NONE");
|
||||
} else {
|
||||
sb.append(grouping.grouping1);
|
||||
if (grouping.grouping2 != grouping.grouping1) {
|
||||
sb.append(',');
|
||||
sb.append(grouping.grouping2);
|
||||
}
|
||||
if (grouping.min2) {
|
||||
sb.append('&');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int skeletonToGrouping(String skeleton, int offset, MacroProps output) {
|
||||
int originalOffset = offset;
|
||||
char c0 = skeleton.charAt(offset++);
|
||||
Grouping result = null;
|
||||
if ('0' <= c0 && c0 <= '9') {
|
||||
char c1 = safeCharAt(skeleton, offset++);
|
||||
if (c1 == ',') {
|
||||
char c2 = safeCharAt(skeleton, offset++);
|
||||
char c3 = safeCharAt(skeleton, offset++);
|
||||
result = GroupingImpl.getInstance((short) (c0 - '0'), (short) (c2 - '0'), c3 == '&');
|
||||
} else {
|
||||
result = GroupingImpl.getInstance((short) (c0 - '0'), (short) (c0 - '0'), c1 == '&');
|
||||
}
|
||||
} else {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
offset += consumeUntil(skeleton, --offset, ' ', sb);
|
||||
String name = sb.toString();
|
||||
if (name.equals("DEFAULT")) {
|
||||
result = Grouping.DEFAULT;
|
||||
} else if (name.equals("DEFAULT_MIN_2_DIGITS")) {
|
||||
result = Grouping.DEFAULT_MIN_2_DIGITS;
|
||||
} else if (name.equals("NONE")) {
|
||||
result = Grouping.NONE;
|
||||
}
|
||||
}
|
||||
output.grouping = result;
|
||||
return offset - originalOffset;
|
||||
}
|
||||
|
||||
private static void paddingToSkeleton(Padding value, StringBuilder sb) {
|
||||
PaddingImpl padding = (PaddingImpl) value;
|
||||
if (padding == Padding.NONE) {
|
||||
sb.append("NONE");
|
||||
return;
|
||||
}
|
||||
sb.append("CP:");
|
||||
// TODO: Handle padding strings that contain ':'
|
||||
sb.append(padding.paddingString);
|
||||
sb.append(':');
|
||||
sb.append(padding.targetWidth);
|
||||
sb.append(':');
|
||||
sb.append(padding.position.name());
|
||||
}
|
||||
|
||||
private static void integerWidthToSkeleton(IntegerWidth value, StringBuilder sb) {
|
||||
IntegerWidthImpl impl = (IntegerWidthImpl) value;
|
||||
sb.append(impl.minInt);
|
||||
if (impl.maxInt != impl.minInt) {
|
||||
sb.append('-');
|
||||
if (impl.maxInt < Integer.MAX_VALUE) {
|
||||
sb.append(impl.maxInt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void symbolsToSkeleton(Object value, StringBuilder sb) {
|
||||
if (value instanceof DecimalFormatSymbols) {
|
||||
// TODO: Check to see if any of the symbols are not default?
|
||||
sb.append("loc:");
|
||||
sb.append(((DecimalFormatSymbols) value).getULocale());
|
||||
} else {
|
||||
sb.append("ns:");
|
||||
sb.append(((NumberingSystem) value).getName());
|
||||
}
|
||||
}
|
||||
|
||||
private static void unitWidthToSkeleton(FormatWidth value, StringBuilder sb) {
|
||||
sb.append(value.name());
|
||||
}
|
||||
|
||||
private static void signToSkeleton(SignDisplay value, StringBuilder sb) {
|
||||
sb.append(value.name());
|
||||
}
|
||||
|
||||
private static void decimalToSkeleton(DecimalMarkDisplay value, StringBuilder sb) {
|
||||
sb.append(value.name());
|
||||
}
|
||||
|
||||
private static char safeCharAt(String str, int offset) {
|
||||
if (offset < str.length()) {
|
||||
return str.charAt(offset);
|
||||
} else {
|
||||
return ' ';
|
||||
}
|
||||
}
|
||||
|
||||
private static int consumeUntil(String skeleton, int offset, char brk, StringBuilder sb) {
|
||||
int originalOffset = offset;
|
||||
char c = safeCharAt(skeleton, offset++);
|
||||
while (c != brk) {
|
||||
sb.append(c);
|
||||
c = safeCharAt(skeleton, offset++);
|
||||
}
|
||||
return offset - originalOffset;
|
||||
}
|
||||
}
|
263
icu4j/main/classes/core/src/newapi/impl/Worker1.java
Normal file
263
icu4j/main/classes/core/src/newapi/impl/Worker1.java
Normal file
@ -0,0 +1,263 @@
|
||||
// © 2017 and later: Unicode, Inc. and others.
|
||||
// License & terms of use: http://www.unicode.org/copyright.html#License
|
||||
package newapi.impl;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.ibm.icu.impl.StandardPlural;
|
||||
import com.ibm.icu.impl.number.FormatQuantity;
|
||||
import com.ibm.icu.impl.number.LdmlPatternInfo;
|
||||
import com.ibm.icu.impl.number.LdmlPatternInfo.PatternParseResult;
|
||||
import com.ibm.icu.impl.number.Modifier;
|
||||
import com.ibm.icu.impl.number.NumberStringBuilder;
|
||||
import com.ibm.icu.impl.number.modifiers.ConstantAffixModifier;
|
||||
import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
|
||||
import com.ibm.icu.text.CompactDecimalFormat.CompactType;
|
||||
import com.ibm.icu.text.DecimalFormatSymbols;
|
||||
import com.ibm.icu.text.MeasureFormat.FormatWidth;
|
||||
import com.ibm.icu.text.NumberFormat;
|
||||
import com.ibm.icu.text.NumberingSystem;
|
||||
import com.ibm.icu.text.PluralRules;
|
||||
import com.ibm.icu.util.Currency;
|
||||
import com.ibm.icu.util.Currency.CurrencyUsage;
|
||||
import com.ibm.icu.util.Dimensionless;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
|
||||
import newapi.NumberFormatter.DecimalMarkDisplay;
|
||||
import newapi.NumberFormatter.Grouping;
|
||||
import newapi.NumberFormatter.NotationCompact;
|
||||
import newapi.NumberFormatter.NotationScientific;
|
||||
import newapi.NumberFormatter.Rounding;
|
||||
import newapi.NumberFormatter.SignDisplay;
|
||||
|
||||
public class Worker1 {
|
||||
|
||||
public static Worker1 fromMacros(MacroProps macros) {
|
||||
return new Worker1(make(macros, true));
|
||||
}
|
||||
|
||||
public static MicroProps applyStatic(
|
||||
MacroProps macros, FormatQuantity inValue, NumberStringBuilder outString) {
|
||||
MicroProps micros = make(macros, false).withQuantity(inValue);
|
||||
applyStatic(micros, inValue, outString);
|
||||
return micros;
|
||||
}
|
||||
|
||||
private static final Currency DEFAULT_CURRENCY = Currency.getInstance("XXX");
|
||||
|
||||
final QuantityChain microsGenerator;
|
||||
|
||||
private Worker1(QuantityChain microsGenerator) {
|
||||
this.microsGenerator = microsGenerator;
|
||||
}
|
||||
|
||||
public MicroProps apply(FormatQuantity inValue, NumberStringBuilder outString) {
|
||||
MicroProps micros = microsGenerator.withQuantity(inValue);
|
||||
applyStatic(micros, inValue, outString);
|
||||
return micros;
|
||||
}
|
||||
|
||||
//////////
|
||||
|
||||
private static QuantityChain make(MacroProps input, boolean build) {
|
||||
|
||||
String innerPattern = null;
|
||||
Map<StandardPlural, Modifier> outerMods = null;
|
||||
Rounding defaultRounding = Rounding.NONE;
|
||||
Currency currency = DEFAULT_CURRENCY;
|
||||
FormatWidth unitWidth = null;
|
||||
boolean perMille = false;
|
||||
PluralRules rules = input.rules;
|
||||
|
||||
MicroProps micros = new MicroProps();
|
||||
QuantityChain chain = micros;
|
||||
|
||||
// Copy over the simple settings
|
||||
micros.sign = input.sign == null ? SignDisplay.AUTO : input.sign;
|
||||
micros.decimal = input.decimal == null ? DecimalMarkDisplay.AUTO : input.decimal;
|
||||
micros.multiplier = 0;
|
||||
micros.integerWidth =
|
||||
input.integerWidth == null
|
||||
? IntegerWidthImpl.DEFAULT
|
||||
: (IntegerWidthImpl) input.integerWidth;
|
||||
|
||||
if (input.unit == null || input.unit == Dimensionless.BASE) {
|
||||
// No units; default format
|
||||
innerPattern = NumberFormat.getPatternForStyle(input.loc, NumberFormat.NUMBERSTYLE);
|
||||
} else if (input.unit == Dimensionless.PERCENT) {
|
||||
// Percent
|
||||
innerPattern = NumberFormat.getPatternForStyle(input.loc, NumberFormat.PERCENTSTYLE);
|
||||
micros.multiplier += 2;
|
||||
} else if (input.unit == Dimensionless.PERMILLE) {
|
||||
// Permille
|
||||
innerPattern = NumberFormat.getPatternForStyle(input.loc, NumberFormat.PERCENTSTYLE);
|
||||
micros.multiplier += 3;
|
||||
perMille = true;
|
||||
} else if (input.unit instanceof Currency && input.unitWidth != FormatWidth.WIDE) {
|
||||
// Narrow, short, or ISO currency.
|
||||
// TODO: Accounting style?
|
||||
innerPattern = NumberFormat.getPatternForStyle(input.loc, NumberFormat.CURRENCYSTYLE);
|
||||
defaultRounding = Rounding.currency(CurrencyUsage.STANDARD);
|
||||
currency = (Currency) input.unit;
|
||||
micros.useCurrency = true;
|
||||
unitWidth = (input.unitWidth == null) ? FormatWidth.NARROW : input.unitWidth;
|
||||
} else if (input.unit instanceof Currency) {
|
||||
// Currency long name
|
||||
innerPattern = NumberFormat.getPatternForStyle(input.loc, NumberFormat.NUMBERSTYLE);
|
||||
outerMods = DataUtils.getCurrencyLongNameModifiers(input.loc, (Currency) input.unit);
|
||||
defaultRounding = Rounding.currency(CurrencyUsage.STANDARD);
|
||||
currency = (Currency) input.unit;
|
||||
micros.useCurrency = true;
|
||||
unitWidth = input.unitWidth = FormatWidth.WIDE;
|
||||
} else {
|
||||
// MeasureUnit
|
||||
innerPattern = NumberFormat.getPatternForStyle(input.loc, NumberFormat.NUMBERSTYLE);
|
||||
unitWidth = (input.unitWidth == null) ? FormatWidth.SHORT : input.unitWidth;
|
||||
outerMods = DataUtils.getMeasureUnitModifiers(input.loc, input.unit, unitWidth);
|
||||
}
|
||||
|
||||
// Parse the pattern, which is used for grouping and affixes only.
|
||||
PatternParseResult patternInfo = LdmlPatternInfo.parse(innerPattern);
|
||||
|
||||
// Symbols
|
||||
if (input.symbols == null) {
|
||||
micros.symbols = DecimalFormatSymbols.getInstance(input.loc);
|
||||
} else if (input.symbols instanceof DecimalFormatSymbols) {
|
||||
micros.symbols = (DecimalFormatSymbols) input.symbols;
|
||||
} else if (input.symbols instanceof NumberingSystem) {
|
||||
// TODO: Do this more efficiently. Will require modifying DecimalFormatSymbols.
|
||||
NumberingSystem ns = (NumberingSystem) input.symbols;
|
||||
ULocale temp = input.loc.setKeywordValue("numbers", ns.getName());
|
||||
micros.symbols = DecimalFormatSymbols.getInstance(temp);
|
||||
} else {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
// TODO: Normalize the currency (accept symbols from DecimalFormatSymbols)?
|
||||
// currency = CustomSymbolCurrency.resolve(currency, input.loc, micros.symbols);
|
||||
|
||||
// Multiplier (compatibility mode value).
|
||||
// An int magnitude multiplier is used when not in compatibility mode to
|
||||
// reduce object creations.
|
||||
if (input.multiplier != null) {
|
||||
// TODO: Make sure this is thread safe.
|
||||
chain = input.multiplier.chain(chain);
|
||||
}
|
||||
|
||||
// Rounding strategy
|
||||
if (input.rounding != null) {
|
||||
micros.rounding = RoundingImpl.normalizeType(input.rounding, currency);
|
||||
} else if (input.notation instanceof NotationCompact) {
|
||||
micros.rounding = RoundingImpl.RoundingImplFractionSignificant.COMPACT_STRATEGY;
|
||||
} else {
|
||||
micros.rounding = RoundingImpl.normalizeType(defaultRounding, currency);
|
||||
}
|
||||
|
||||
// Grouping strategy
|
||||
if (input.grouping != null) {
|
||||
micros.grouping = GroupingImpl.normalizeType(input.grouping, patternInfo);
|
||||
} else if (input.notation instanceof NotationCompact) {
|
||||
// Compact notation uses minGrouping by default since ICU 59
|
||||
micros.grouping = GroupingImpl.normalizeType(Grouping.DEFAULT_MIN_2_DIGITS, patternInfo);
|
||||
} else {
|
||||
micros.grouping = GroupingImpl.normalizeType(Grouping.DEFAULT, patternInfo);
|
||||
}
|
||||
|
||||
// Inner modifier (scientific notation)
|
||||
if (input.notation instanceof NotationScientific) {
|
||||
assert input.notation instanceof NotationImpl.NotationScientificImpl;
|
||||
chain =
|
||||
ScientificImpl.getInstance(
|
||||
(NotationImpl.NotationScientificImpl) input.notation, micros.symbols, build)
|
||||
.chain(chain);
|
||||
} else {
|
||||
// No inner modifier required
|
||||
micros.modInner = ConstantAffixModifier.EMPTY;
|
||||
}
|
||||
|
||||
// Middle modifier (patterns, positive/negative, currency symbols, percent)
|
||||
MurkyModifier murkyMod = new MurkyModifier(false);
|
||||
murkyMod.setPatternInfo((input.affixProvider != null) ? input.affixProvider : patternInfo);
|
||||
murkyMod.setPatternAttributes(micros.sign, perMille);
|
||||
if (murkyMod.needsPlurals()) {
|
||||
if (rules == null) {
|
||||
// Lazily create PluralRules
|
||||
rules = PluralRules.forLocale(input.loc);
|
||||
}
|
||||
murkyMod.setSymbols(micros.symbols, currency, unitWidth, rules);
|
||||
} else {
|
||||
murkyMod.setSymbols(micros.symbols, currency, unitWidth, null);
|
||||
}
|
||||
if (build) {
|
||||
chain = murkyMod.createImmutable().chain(chain);
|
||||
} else {
|
||||
chain = murkyMod.chain(chain);
|
||||
}
|
||||
|
||||
// Outer modifier (CLDR units and currency long names)
|
||||
if (outerMods != null) {
|
||||
if (rules == null) {
|
||||
// Lazily create PluralRules
|
||||
rules = PluralRules.forLocale(input.loc);
|
||||
}
|
||||
chain = new QuantityDependentModOuter(outerMods, rules).chain(chain);
|
||||
} else {
|
||||
// No outer modifier required
|
||||
micros.modOuter = ConstantAffixModifier.EMPTY;
|
||||
}
|
||||
|
||||
// Padding strategy
|
||||
if (input.padding != null) {
|
||||
micros.padding = (PaddingImpl) input.padding;
|
||||
} else {
|
||||
micros.padding = PaddingImpl.NONE;
|
||||
}
|
||||
|
||||
// Compact notation
|
||||
// NOTE: Compact notation can (but might not) override the middle modifier and rounding.
|
||||
// It therefore needs to go at the end of the chain.
|
||||
if (input.notation instanceof NotationCompact) {
|
||||
assert input.notation instanceof NotationImpl.NotationCompactImpl;
|
||||
if (rules == null) {
|
||||
// Lazily create PluralRules
|
||||
rules = PluralRules.forLocale(input.loc);
|
||||
}
|
||||
CompactStyle compactStyle = ((NotationImpl.NotationCompactImpl) input.notation).compactStyle;
|
||||
CompactImpl worker;
|
||||
if (compactStyle == null) {
|
||||
// Use compact custom data
|
||||
worker =
|
||||
CompactImpl.getInstance(
|
||||
((NotationImpl.NotationCompactImpl) input.notation).compactCustomData, rules);
|
||||
} else {
|
||||
CompactType compactType =
|
||||
(input.unit instanceof Currency) ? CompactType.CURRENCY : CompactType.DECIMAL;
|
||||
worker = CompactImpl.getInstance(input.loc, compactType, compactStyle, rules);
|
||||
}
|
||||
if (build) {
|
||||
worker.precomputeAllModifiers(murkyMod);
|
||||
}
|
||||
chain = worker.chain(chain);
|
||||
}
|
||||
|
||||
if (build) {
|
||||
micros.enableCloneInChain();
|
||||
}
|
||||
|
||||
return chain;
|
||||
}
|
||||
|
||||
//////////
|
||||
|
||||
private static int applyStatic(
|
||||
MicroProps micros, FormatQuantity inValue, NumberStringBuilder outString) {
|
||||
inValue.adjustMagnitude(micros.multiplier);
|
||||
micros.rounding.apply(inValue);
|
||||
inValue.setIntegerLength(micros.integerWidth.minInt, micros.integerWidth.maxInt);
|
||||
int length = PositiveDecimalImpl.apply(micros, inValue, outString);
|
||||
// NOTE: When range formatting is added, these modifiers can bubble up.
|
||||
// For now, apply them all here at once.
|
||||
length += micros.padding.applyModsAndMaybePad(micros, outString, 0, length);
|
||||
return length;
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
// © 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.LdmlPatternInfo;
|
||||
import com.ibm.icu.text.DecimalFormatSymbols;
|
||||
import com.ibm.icu.text.MeasureFormat.FormatWidth;
|
||||
import com.ibm.icu.util.Currency;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
|
||||
import newapi.NumberFormatter.SignDisplay;
|
||||
import newapi.impl.MurkyModifier;
|
||||
|
||||
public class MurkyModifierTest {
|
||||
|
||||
@Test
|
||||
public void basic() {
|
||||
MurkyModifier murky = new MurkyModifier(false);
|
||||
murky.setPatternInfo(LdmlPatternInfo.parse("a0b"));
|
||||
murky.setPatternAttributes(SignDisplay.AUTO, false);
|
||||
murky.setSymbols(
|
||||
DecimalFormatSymbols.getInstance(ULocale.ENGLISH),
|
||||
Currency.getInstance("USD"),
|
||||
FormatWidth.SHORT,
|
||||
null);
|
||||
murky.setNumberProperties(false, null);
|
||||
assertEquals("a", murky.getPrefix());
|
||||
assertEquals("b", murky.getSuffix());
|
||||
murky.setPatternAttributes(SignDisplay.ALWAYS_SHOWN, false);
|
||||
assertEquals("+a", murky.getPrefix());
|
||||
assertEquals("b", murky.getSuffix());
|
||||
murky.setNumberProperties(true, null);
|
||||
assertEquals("-a", murky.getPrefix());
|
||||
assertEquals("b", murky.getSuffix());
|
||||
murky.setPatternAttributes(SignDisplay.NEVER_SHOWN, false);
|
||||
assertEquals("a", murky.getPrefix());
|
||||
assertEquals("b", murky.getSuffix());
|
||||
|
||||
murky.setPatternInfo(LdmlPatternInfo.parse("a0b;c-0d"));
|
||||
murky.setPatternAttributes(SignDisplay.AUTO, false);
|
||||
murky.setNumberProperties(false, null);
|
||||
assertEquals("a", murky.getPrefix());
|
||||
assertEquals("b", murky.getSuffix());
|
||||
murky.setPatternAttributes(SignDisplay.ALWAYS_SHOWN, false);
|
||||
assertEquals("c+", murky.getPrefix());
|
||||
assertEquals("d", murky.getSuffix());
|
||||
murky.setNumberProperties(true, null);
|
||||
assertEquals("c-", murky.getPrefix());
|
||||
assertEquals("d", murky.getSuffix());
|
||||
murky.setPatternAttributes(SignDisplay.NEVER_SHOWN, false);
|
||||
assertEquals("c-", murky.getPrefix()); // TODO: What should this behavior be?
|
||||
assertEquals("d", murky.getSuffix());
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user