// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
-package newapi.impl;
+package com.ibm.icu.impl.number.modifiers;
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;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.ULocale.Category;
-import newapi.NumberFormatter.LocalizedNumberFormatter;
-import newapi.NumberFormatter.NumberFormatterResult;
+import newapi.FormattedNumber;
+import newapi.LocalizedNumberFormatter;
+import newapi.NumberFormatter;
+import newapi.NumberPropertyMapper;
import newapi.impl.MacroProps;
-import newapi.impl.NumberFormatterImpl;
-import newapi.impl.NumberPropertyMapper;
/**
* {@icuenhanced java.text.DecimalFormat}.{@icu _usage_} <code>DecimalFormat</code> is the primary
*/
@Override
public StringBuffer format(double number, StringBuffer result, FieldPosition fieldPosition) {
- NumberFormatterResult output = formatter.format(number);
+ FormattedNumber output = formatter.format(number);
output.populateFieldPosition(fieldPosition, result.length());
output.appendTo(result);
return result;
*/
@Override
public StringBuffer format(long number, StringBuffer result, FieldPosition fieldPosition) {
- NumberFormatterResult output = formatter.format(number);
+ FormattedNumber output = formatter.format(number);
output.populateFieldPosition(fieldPosition, result.length());
output.appendTo(result);
return result;
*/
@Override
public StringBuffer format(BigInteger number, StringBuffer result, FieldPosition fieldPosition) {
- NumberFormatterResult output = formatter.format(number);
+ FormattedNumber output = formatter.format(number);
output.populateFieldPosition(fieldPosition, result.length());
output.appendTo(result);
return result;
@Override
public StringBuffer format(
java.math.BigDecimal number, StringBuffer result, FieldPosition fieldPosition) {
- NumberFormatterResult output = formatter.format(number);
+ FormattedNumber output = formatter.format(number);
output.populateFieldPosition(fieldPosition, result.length());
output.appendTo(result);
return result;
*/
@Override
public StringBuffer format(BigDecimal number, StringBuffer result, FieldPosition fieldPosition) {
- NumberFormatterResult output = formatter.format(number);
+ FormattedNumber output = formatter.format(number);
output.populateFieldPosition(fieldPosition, result.length());
output.appendTo(result);
return result;
public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
if (!(obj instanceof Number)) throw new IllegalArgumentException();
Number number = (Number) obj;
- NumberFormatterResult output = formatter.format(number);
+ FormattedNumber output = formatter.format(number);
return output.toAttributedCharacterIterator();
}
*/
@Override
public StringBuffer format(CurrencyAmount currAmt, StringBuffer toAppendTo, FieldPosition pos) {
- NumberFormatterResult output = formatter.format(currAmt);
+ FormattedNumber output = formatter.format(currAmt);
output.populateFieldPosition(pos, toAppendTo.length());
output.appendTo(toAppendTo);
return toAppendTo;
locale = symbols.getULocale();
}
assert locale != null;
- formatter = NumberFormatterImpl.fromMacros(macros).locale(locale);
+ formatter = NumberFormatter.with().macros(macros).locale(locale);
}
/**
--- /dev/null
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package newapi;
+
+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.MurkyModifier.ImmutableMurkyModifier;
+import newapi.impl.CompactData;
+import newapi.impl.MicroProps;
+import newapi.impl.QuantityChain;
+
+public class CompactNotation extends Notation {
+
+ final CompactStyle compactStyle;
+ final Map<String, Map<String, String>> compactCustomData;
+
+ public CompactNotation(CompactStyle compactStyle) {
+ compactCustomData = null;
+ this.compactStyle = compactStyle;
+ }
+
+ public CompactNotation(Map<String, Map<String, String>> compactCustomData) {
+ compactStyle = null;
+ this.compactCustomData = compactCustomData;
+ }
+
+ /* package-private */ QuantityChain withLocaleData(ULocale dataLocale, CompactType compactType, PluralRules rules,
+ MurkyModifier buildReference, QuantityChain parent) {
+ CompactData data;
+ if (compactStyle != null) {
+ data = CompactData.getInstance(dataLocale, compactType, compactStyle);
+ } else {
+ data = CompactData.getInstance(compactCustomData);
+ }
+ return new CompactImpl(data, rules, buildReference, parent);
+ }
+
+ private static class CompactImpl implements QuantityChain {
+
+ private static class CompactModInfo {
+ public ImmutableMurkyModifier mod;
+ public int numDigits;
+ }
+
+ final PluralRules rules;
+ final CompactData data;
+ final Map<String, CompactModInfo> precomputedMods;
+ final QuantityChain parent;
+
+ private CompactImpl(CompactData data, PluralRules rules, MurkyModifier buildReference, QuantityChain parent) {
+ this.data = data;
+ this.rules = rules;
+ if (buildReference != null) {
+ // Safe code path
+ precomputedMods = precomputeAllModifiers(data, buildReference);
+ } else {
+ // Unsafe code path
+ precomputedMods = null;
+ }
+ this.parent = parent;
+ }
+
+ /** Used by the safe code path */
+ private static Map<String, CompactModInfo> precomputeAllModifiers(CompactData data,
+ MurkyModifier buildReference) {
+ Map<String, CompactModInfo> precomputedMods = new HashMap<String, CompactModInfo>();
+ Set<String> allPatterns = data.getAllPatterns();
+ for (String patternString : allPatterns) {
+ CompactModInfo info = new CompactModInfo();
+ PatternParseResult patternInfo = LdmlPatternInfo.parse(patternString);
+ buildReference.setPatternInfo(patternInfo);
+ info.mod = buildReference.createImmutable();
+ info.numDigits = patternInfo.positive.totalIntegerDigits;
+ precomputedMods.put(patternString, info);
+ }
+ return precomputedMods;
+ }
+
+ @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 = Rounder.constructPassThrough();
+
+ return micros;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package newapi;
+
+import com.ibm.icu.util.Currency;
+import com.ibm.icu.util.Currency.CurrencyUsage;
+import com.ibm.icu.util.CurrencyAmount;
+import com.ibm.icu.util.Measure;
+import com.ibm.icu.util.MeasureUnit;
+
+/** A rounding strategy parameterized by a currency. */
+public abstract class CurrencyRounder extends Rounder {
+
+ /* package-private */ CurrencyRounder() {
+ }
+
+ /**
+ * 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 Rounder withCurrency(Currency currency) {
+ if (currency != null) {
+ return constructFromCurrency(this, currency);
+ } else {
+ throw new IllegalArgumentException("Currency must not be null");
+ }
+ };
+}
\ No newline at end of file
--- /dev/null
+// © 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.text.AttributedCharacterIterator;
+import java.text.FieldPosition;
+import java.util.Arrays;
+
+import com.ibm.icu.impl.number.FormatQuantity;
+import com.ibm.icu.impl.number.NumberStringBuilder;
+import com.ibm.icu.text.PluralRules.IFixedDecimal;
+import com.ibm.icu.util.ICUUncheckedIOException;
+
+import newapi.impl.MicroProps;
+
+public class FormattedNumber {
+ NumberStringBuilder nsb;
+ FormatQuantity fq;
+ MicroProps micros;
+
+ FormattedNumber(NumberStringBuilder nsb, FormatQuantity 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 void populateFieldPosition(FieldPosition fieldPosition) {
+ populateFieldPosition(fieldPosition, 0);
+ }
+
+ /**
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
+ public void populateFieldPosition(FieldPosition fieldPosition, int offset) {
+ nsb.populateFieldPosition(fieldPosition, offset);
+ fq.populateUFieldPosition(fieldPosition);
+ }
+
+ public AttributedCharacterIterator toAttributedCharacterIterator() {
+ return nsb.getIterator();
+ }
+
+ public BigDecimal toBigDecimal() {
+ return fq.toBigDecimal();
+ }
+
+ /**
+ * @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;
+ }
+
+ @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 FormattedNumber))
+ return false;
+ // NumberStringBuilder and BigDecimal are mutable, so we can't call
+ // #equals() or #hashCode() on them directly.
+ FormattedNumber _other = (FormattedNumber) other;
+ return Arrays.equals(nsb.toCharArray(), _other.nsb.toCharArray())
+ ^ Arrays.equals(nsb.toFieldArray(), _other.nsb.toFieldArray())
+ ^ fq.toBigDecimal().equals(_other.fq.toBigDecimal());
+ }
+}
\ No newline at end of file
--- /dev/null
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package newapi;
+
+/**
+ * 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 abstract class FractionRounder extends Rounder {
+
+ /* package-private */ FractionRounder() {
+ }
+
+ /**
+ * 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 Rounder withMinFigures(int minFigures) {
+ if (minFigures > 0 && minFigures <= MAX_VALUE) {
+ return constructFractionSignificant(this, minFigures, -1);
+ } else {
+ throw new IllegalArgumentException("Significant digits must be between 0 and " + MAX_VALUE);
+ }
+ }
+
+ /**
+ * 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
+ * Round the number to no more than this number of significant figures.
+ * @return An immutable object for chaining.
+ */
+ public Rounder withMaxFigures(int maxFigures) {
+ if (maxFigures > 0 && maxFigures <= MAX_VALUE) {
+ return constructFractionSignificant(this, -1, maxFigures);
+ } else {
+ throw new IllegalArgumentException("Significant digits must be between 0 and " + MAX_VALUE);
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package newapi;
+
+import com.ibm.icu.impl.number.FormatQuantity;
+import com.ibm.icu.impl.number.LdmlPatternInfo.PatternParseResult;
+
+public class Grouper {
+
+ // Conveniences for Java handling of bytes
+ private static final byte N2 = -2;
+ private static final byte N1 = -1;
+ private static final byte B2 = 2;
+ private static final byte B3 = 3;
+
+ private static final Grouper DEFAULTS = new Grouper(N2, N2, false);
+ private static final Grouper MIN2 = new Grouper(N2, N2, true);
+ private static final Grouper NONE = new Grouper(N1, N1, false);
+
+ private final byte grouping1; // -2 means "needs locale data"; -1 means "no grouping"
+ private final byte grouping2;
+ private final boolean min2;
+
+ private Grouper(byte grouping1, byte grouping2, boolean min2) {
+ this.grouping1 = grouping1;
+ this.grouping2 = grouping2;
+ this.min2 = min2;
+ }
+
+ public static Grouper defaults() {
+ return DEFAULTS;
+ }
+
+ public static Grouper min2() {
+ return MIN2;
+ }
+
+ public static Grouper none() {
+ return NONE;
+ }
+
+ //////////////////////////
+ // PACKAGE-PRIVATE APIS //
+ //////////////////////////
+
+ private static final Grouper GROUPING_3 = new Grouper(B3, B3, false);
+ private static final Grouper GROUPING_3_2 = new Grouper(B3, B2, false);
+ private static final Grouper GROUPING_3_MIN2 = new Grouper(B3, B3, true);
+ private static final Grouper GROUPING_3_2_MIN2 = new Grouper(B3, B2, true);
+
+ static Grouper getInstance(byte grouping1, byte 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 Grouper(grouping1, grouping2, min2);
+ }
+ }
+
+ static Grouper normalizeType(Grouper grouping, PatternParseResult patternInfo) {
+ return grouping.withLocaleData(patternInfo);
+ }
+
+ Grouper withLocaleData(PatternParseResult patternInfo) {
+ if (grouping1 != -2) {
+ return this;
+ }
+ // TODO: short or byte?
+ byte grouping1 = (byte) (patternInfo.positive.groupingSizes & 0xffff);
+ byte grouping2 = (byte) ((patternInfo.positive.groupingSizes >>> 16) & 0xffff);
+ byte grouping3 = (byte) ((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) {
+ 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);
+ }
+ }
\ No newline at end of file
--- /dev/null
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package newapi;
+
+@SuppressWarnings("unused")
+public class IntegerWidth {
+
+ private static final IntegerWidth DEFAULT = new IntegerWidth(1, -1);
+
+ final int minInt;
+ final int maxInt;
+
+ private IntegerWidth(int minInt, int maxInt) {
+ this.minInt = minInt;
+ this.maxInt = maxInt;
+ }
+
+ public static IntegerWidth zeroFillTo(int minInt) {
+ if (minInt == 1) {
+ return DEFAULT;
+ } else if (minInt >= 0 && minInt < Rounder.MAX_VALUE) {
+ return new IntegerWidth(minInt, -1);
+ } else {
+ throw new IllegalArgumentException("Integer digits must be between 0 and " + Rounder.MAX_VALUE);
+ }
+ }
+
+ public IntegerWidth truncateAt(int maxInt) {
+ if (maxInt == this.maxInt) {
+ return this;
+ } else if (maxInt >= 0 && maxInt < Rounder.MAX_VALUE) {
+ return new IntegerWidth(minInt, maxInt);
+ } else if (maxInt == -1) {
+ return new IntegerWidth(minInt, maxInt);
+ } else {
+ throw new IllegalArgumentException("Integer digits must be between 0 and " + Rounder.MAX_VALUE);
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package newapi;
+
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicLongFieldUpdater;
+
+import com.ibm.icu.impl.number.FormatQuantity;
+import com.ibm.icu.impl.number.FormatQuantity4;
+import com.ibm.icu.impl.number.NumberStringBuilder;
+import com.ibm.icu.util.Measure;
+import com.ibm.icu.util.MeasureUnit;
+
+import newapi.impl.MacroProps;
+import newapi.impl.MicroProps;
+
+public class LocalizedNumberFormatter extends NumberFormatterSettings<LocalizedNumberFormatter> {
+
+ static final AtomicLongFieldUpdater<LocalizedNumberFormatter> callCount = AtomicLongFieldUpdater
+ .newUpdater(LocalizedNumberFormatter.class, "callCountInternal");
+
+ volatile long callCountInternal; // do not access directly; use callCount instead
+ volatile LocalizedNumberFormatter savedWithUnit;
+ volatile Worker1 compiled;
+
+ LocalizedNumberFormatter(NumberFormatterSettings<?> parent, int key, Object value) {
+ super(parent, key, value);
+ }
+
+ public FormattedNumber format(long input) {
+ return format(new FormatQuantity4(input));
+ }
+
+ public FormattedNumber format(double input) {
+ return format(new FormatQuantity4(input));
+ }
+
+ public FormattedNumber format(Number input) {
+ return format(new FormatQuantity4(input));
+ }
+
+ public FormattedNumber format(Measure input) {
+ MeasureUnit unit = input.getUnit();
+ Number number = input.getNumber();
+ // Use this formatter if possible
+ if (Objects.equals(resolve().unit, unit)) {
+ return format(number);
+ }
+ // 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.
+ LocalizedNumberFormatter withUnit = savedWithUnit;
+ if (withUnit == null || !Objects.equals(withUnit.resolve().unit, unit)) {
+ withUnit = new LocalizedNumberFormatter(this, KEY_UNIT, unit);
+ savedWithUnit = withUnit;
+ }
+ return withUnit.format(number);
+ }
+
+ /**
+ * This is the core entrypoint to the number formatting pipeline. It performs self-regulation: a static code path
+ * for the first few calls, and compiling a more efficient data structure if called repeatedly.
+ *
+ * @param fq
+ * The quantity to be formatted.
+ * @return The formatted number result.
+ *
+ * @internal
+ * @deprecated ICU 60 This API is ICU internal only.
+ */
+ @Deprecated
+ public FormattedNumber format(FormatQuantity fq) {
+ MacroProps macros = resolve();
+ NumberStringBuilder string = new NumberStringBuilder();
+ long currentCount = callCount.incrementAndGet(this);
+ MicroProps micros;
+ if (currentCount == macros.threshold.longValue()) {
+ compiled = Worker1.fromMacros(macros);
+ micros = compiled.apply(fq, string);
+ } else if (compiled != null) {
+ micros = compiled.apply(fq, string);
+ } else {
+ micros = Worker1.applyStatic(macros, fq, string);
+ }
+ return new FormattedNumber(string, fq, micros);
+ }
+
+ @Override
+ protected LocalizedNumberFormatter create(int key, Object value) {
+ return new LocalizedNumberFormatter(this, key, value);
+ }
+
+ /**
+ * @internal
+ * @deprecated ICU 60 This API is ICU internal only.
+ */
+ @Deprecated
+ public static class Internal extends LocalizedNumberFormatter {
+
+ /**
+ * @internal
+ * @deprecated ICU 60 This API is ICU internal only.
+ */
+ @Deprecated
+ public Internal(NumberFormatterSettings<?> parent, int key, Object value) {
+ super(parent, key, value);
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package newapi;
+
+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.FormatQuantity;
+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.text.PluralRules;
+import com.ibm.icu.util.Currency;
+import com.ibm.icu.util.MeasureUnit;
+import com.ibm.icu.util.ULocale;
+
+import newapi.impl.MeasureData;
+import newapi.impl.MicroProps;
+import newapi.impl.QuantityChain;
+
+class MurkyLongNameHandler implements QuantityChain {
+
+ private final Map<StandardPlural, Modifier> data;
+ /* unsafe */ PluralRules rules;
+ /* unsafe */ QuantityChain parent;
+
+ private MurkyLongNameHandler(Map<StandardPlural, Modifier> data) {
+ this.data = data;
+ }
+
+ public static MurkyLongNameHandler 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 new MurkyLongNameHandler(result);
+ }
+
+ public static MurkyLongNameHandler 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 new MurkyLongNameHandler(result);
+ }
+
+ public QuantityChain withLocaleData(PluralRules rules, boolean safe, QuantityChain parent) {
+ if (safe) {
+ // Safe code path: return a new object
+ return new ImmutableLongNameHandler(data, rules, parent);
+ } else {
+ // Unsafe code path: re-use this object!
+ this.rules = rules;
+ 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;
+ }
+
+ public static class ImmutableLongNameHandler implements QuantityChain {
+ final Map<StandardPlural, Modifier> data;
+ final PluralRules rules;
+ final QuantityChain parent;
+
+ public ImmutableLongNameHandler(Map<StandardPlural, Modifier> data, PluralRules rules, QuantityChain parent) {
+ this.data = data;
+ this.rules = rules;
+ this.parent = parent;
+ }
+
+ @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;
+ }
+ }
+}
--- /dev/null
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package newapi;
+
+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.LdmlPatternInfo;
+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.impl.number.modifiers.CurrencySpacingEnabledModifier;
+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;
+import newapi.impl.AffixPatternProvider;
+import newapi.impl.MicroProps;
+import newapi.impl.QuantityChain;
+
+/**
+ * This class is a {@link Modifier} that wraps a decimal format pattern. It applies the pattern's affixes in
+ * {@link Modifier#apply}.
+ *
+ * <p>
+ * In addition to being a Modifier, this class contains the business logic for substituting the correct locale symbols
+ * into the affixes of the decimal format pattern.
+ *
+ * <p>
+ * In order to use this class, create a new instance and call the following four setters: {@link #setPatternInfo},
+ * {@link #setPatternAttributes}, {@link #setSymbols}, and {@link #setNumberProperties}. After calling these four
+ * setters, the instance will be ready for use as a Modifier.
+ *
+ * <p>
+ * 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! Instead, you can obtain a safe, immutable decimal format pattern modifier by calling
+ * {@link MurkyModifier#createImmutable}, in effect treating this instance as a builder for the immutable variant.
+ *
+ * FIXME: Make this package-private
+ */
+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;
+
+ /**
+ * @param isStrong
+ * Whether the modifier should be considered strong. For more information, see
+ * {@link Modifier#isStrong()}. Most of the time, decimal format pattern modifiers should be considered
+ * as non-strong.
+ */
+ public MurkyModifier(boolean isStrong) {
+ this.isStrong = isStrong;
+ }
+
+ /**
+ * Sets a reference to the parsed decimal format pattern, usually obtained from
+ * {@link LdmlPatternInfo#parse(String)}, but any implementation of {@link AffixPatternProvider} is accepted.
+ */
+ public void setPatternInfo(AffixPatternProvider patternInfo) {
+ this.patternInfo = patternInfo;
+ }
+
+ /**
+ * Sets attributes that imply changes to the literal interpretation of the pattern string affixes.
+ *
+ * @param signDisplay
+ * Whether to force a plus sign on positive numbers.
+ * @param perMille
+ * Whether to substitute the percent sign in the pattern with a permille sign.
+ */
+ public void setPatternAttributes(SignDisplay signDisplay, boolean perMille) {
+ this.signDisplay = signDisplay;
+ this.perMilleReplacesPercent = perMille;
+ }
+
+ /**
+ * Sets locale-specific details that affect the symbols substituted into the pattern string affixes.
+ *
+ * @param symbols
+ * The desired instance of DecimalFormatSymbols.
+ * @param currency
+ * The currency to be used when substituting currency values into the affixes. Cannot be null, but a
+ * bogus currency like "XXX" can be used.
+ * @param unitWidth
+ * The width used to render currencies.
+ * @param rules
+ * Required if the triple currency sign, "¤¤¤", appears in the pattern, which can be determined from the
+ * convenience method {@link #needsPlurals()}.
+ */
+ 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);
+ }
+ }
+ }
+
+ /**
+ * Sets attributes of the current number being processed.
+ *
+ * @param isNegative
+ * Whether the number is negative.
+ * @param plural
+ * The plural form of the number, required only if the pattern contains the triple currency sign, "¤¤¤"
+ * (and as indicated by {@link #needsPlurals()}).
+ */
+ 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);
+ }
+
+ /**
+ * 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 number properties in the current instance are mutated; all other properties
+ * are left untouched.
+ *
+ * <p>
+ * The resulting modifier cannot be used in a QuantityChain.
+ *
+ * @return An immutable that supports both positive and negative numbers.
+ */
+ public ImmutableMurkyModifier createImmutable() {
+ return createImmutableAndChain(null);
+ }
+
+ /**
+ * 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 number properties in the current instance are mutated; all other properties
+ * are left untouched.
+ *
+ * @param parent
+ * The QuantityChain to which to chain this immutable.
+ * @return An immutable that supports both positive and negative numbers.
+ */
+ public ImmutableMurkyModifier createImmutableAndChain(QuantityChain parent) {
+ 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, parent);
+ } 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, parent);
+ }
+ }
+
+ 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, QuantityChain parent) {
+ this.positive = positive;
+ this.negative = negative;
+ this.parent = parent;
+ }
+
+ @Override
+ public MicroProps withQuantity(FormatQuantity quantity) {
+ assert parent != null;
+ 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, QuantityChain parent) {
+ assert mods.length == getModsLength();
+ assert rules != null;
+ this.mods = mods;
+ this.rules = rules;
+ this.parent = parent;
+ }
+
+ 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 MicroProps withQuantity(FormatQuantity quantity) {
+ assert parent != null;
+ 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;
+ }
+ }
+
+ public QuantityChain addToChain(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;
+ }
+
+ @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
+ && 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;
+ } 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();
+ }
+}
--- /dev/null
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package newapi;
+
+import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
+
+import newapi.NumberFormatter.SignDisplay;
+
+public class Notation {
+
+ // FIXME: Support engineering intervals other than 3?
+ private static final ScientificNotation SCIENTIFIC = new ScientificNotation(1, false, 1, SignDisplay.AUTO);
+ private static final ScientificNotation ENGINEERING = new ScientificNotation(3, false, 1, SignDisplay.AUTO);
+ private static final CompactNotation COMPACT_SHORT = new CompactNotation(CompactStyle.SHORT);
+ private static final CompactNotation COMPACT_LONG = new CompactNotation(CompactStyle.LONG);
+ private static final SimpleNotation SIMPLE = new SimpleNotation();
+
+ /* package-private */ Notation() {
+ }
+
+ public static ScientificNotation scientific() {
+ return SCIENTIFIC;
+ }
+
+ public static ScientificNotation engineering() {
+ return ENGINEERING;
+ }
+
+ public static CompactNotation compactShort() {
+ return COMPACT_SHORT;
+ }
+
+ public static CompactNotation compactLong() {
+ return COMPACT_LONG;
+ }
+
+ public static SimpleNotation simple() {
+ return SIMPLE;
+ }
+}
\ No newline at end of file
// 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.FormatQuantity;
-import com.ibm.icu.impl.number.NumberStringBuilder;
-import com.ibm.icu.impl.number.ThingsNeedingNewHome.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);
- }
+ private static final UnlocalizedNumberFormatter BASE = new UnlocalizedNumberFormatter();
// This could possibly be combined into MeasureFormat.FormatWidth
public static enum CurrencyDisplay {
NEVER,
}
- 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();
+ return BASE;
}
public static LocalizedNumberFormatter withLocale(Locale locale) {
- return NumberFormatterImpl.with().locale(locale);
+ return BASE.locale(locale);
}
public static LocalizedNumberFormatter withLocale(ULocale locale) {
- return NumberFormatterImpl.with().locale(locale);
- }
-
- public static class NumberFormatterResult {
- NumberStringBuilder nsb;
- FormatQuantity fq;
- MicroProps micros;
-
- /**
- * @internal
- * @deprecated This API is ICU internal only.
- */
- @Deprecated
- public NumberFormatterResult(NumberStringBuilder nsb, FormatQuantity 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 {
-
- // FIXME
- /** @internal */
- public 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 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) {
- // TODO: Validate the code point
- if (targetWidth >= 0) {
- String paddingString = String.valueOf(Character.toChars(cp));
- return PaddingImpl.getInstance(paddingString, targetWidth, position);
- } else {
- throw new IllegalArgumentException("Padding width must not be negative");
- }
- }
-
- // 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) {
- if (minInt >= 0 && minInt < Rounding.MAX_VALUE) {
- return new IntegerWidthImpl(minInt, Integer.MAX_VALUE);
- } else {
- throw new IllegalArgumentException(
- "Integer digits must be between 0 and " + Rounding.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 {}
+ return BASE.locale(locale);
}
}
--- /dev/null
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package newapi;
+
+import com.ibm.icu.text.DecimalFormatSymbols;
+import com.ibm.icu.text.MeasureFormat.FormatWidth;
+import com.ibm.icu.text.NumberingSystem;
+import com.ibm.icu.util.MeasureUnit;
+import com.ibm.icu.util.ULocale;
+
+import newapi.NumberFormatter.DecimalMarkDisplay;
+import newapi.NumberFormatter.SignDisplay;
+import newapi.impl.MacroProps;
+import newapi.impl.Padder;
+
+public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<?>> {
+
+ 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_ROUNDER = 4;
+ static final int KEY_GROUPER = 5;
+ static final int KEY_PADDER = 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_THRESHOLD = 12;
+ static final int KEY_MAX = 13;
+
+ final NumberFormatterSettings<?> parent;
+ final int key;
+ final Object value;
+ volatile MacroProps resolvedMacros;
+
+ NumberFormatterSettings(NumberFormatterSettings<?> parent, int key, Object value) {
+ this.parent = parent;
+ this.key = key;
+ this.value = value;
+ }
+
+ public T notation(Notation notation) {
+ return create(KEY_NOTATION, notation);
+ }
+
+ public T unit(MeasureUnit unit) {
+ return create(KEY_UNIT, unit);
+ }
+
+ public T rounding(Rounder rounder) {
+ return create(KEY_ROUNDER, rounder);
+ }
+
+ public T grouping(Grouper grouper) {
+ return create(KEY_GROUPER, grouper);
+ }
+
+ public T integerWidth(IntegerWidth style) {
+ return create(KEY_INTEGER, style);
+ }
+
+ public T symbols(DecimalFormatSymbols symbols) {
+ return create(KEY_SYMBOLS, symbols);
+ }
+
+ public T symbols(NumberingSystem ns) {
+ return create(KEY_SYMBOLS, ns);
+ }
+
+ public T unitWidth(FormatWidth style) {
+ return create(KEY_UNIT_WIDTH, style);
+ }
+
+ public T sign(SignDisplay style) {
+ return create(KEY_SIGN, style);
+ }
+
+ public T decimal(DecimalMarkDisplay style) {
+ return create(KEY_DECIMAL, style);
+ }
+
+ /** Internal method to set a starting macros. */
+ public T macros(MacroProps macros) {
+ return create(KEY_MACROS, macros);
+ }
+
+ /** Non-public method */
+ public T padding(Padder padder) {
+ return create(KEY_PADDER, padder);
+ }
+
+ /**
+ * Internal fluent setter to support 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 T threshold(Long threshold) {
+ return create(KEY_THRESHOLD, threshold);
+ }
+
+ public String toSkeleton() {
+ return SkeletonBuilder.macrosToSkeleton(resolve());
+ }
+
+ abstract T create(int key, Object value);
+
+ 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();
+ NumberFormatterSettings<?> current = this;
+ while (current != null) {
+ 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_ROUNDER:
+ if (macros.rounder == null) {
+ macros.rounder = (Rounder) current.value;
+ }
+ break;
+ case KEY_GROUPER:
+ if (macros.grouper == null) {
+ macros.grouper = (Grouper) current.value;
+ }
+ break;
+ case KEY_PADDER:
+ if (macros.padder == null) {
+ macros.padder = (Padder) 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;
+ case KEY_THRESHOLD:
+ if (macros.threshold == null) {
+ macros.threshold = (Long) current.value;
+ }
+ break;
+ default:
+ throw new AssertionError("Unknown key: " + current.key);
+ }
+ current = current.parent;
+ }
+ resolvedMacros = macros;
+ return macros;
+ }
+
+ @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 NumberFormatterSettings)) {
+ return false;
+ }
+ return resolve().equals(((NumberFormatterSettings<?>) other).resolve());
+ }
+}
--- /dev/null
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package newapi;
+
+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.PatternString;
+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.DecimalMarkDisplay;
+import newapi.NumberFormatter.SignDisplay;
+import newapi.Rounder.FractionRounderImpl;
+import newapi.Rounder.IncrementRounderImpl;
+import newapi.Rounder.SignificantRounderImpl;
+import newapi.impl.AffixPatternProvider;
+import newapi.impl.CustomSymbolCurrency;
+import newapi.impl.MacroProps;
+import newapi.impl.MultiplierImpl;
+import newapi.impl.Padder;
+
+/** @author sffc */
+public final class NumberPropertyMapper {
+
+ /** Convenience method to create a NumberFormatter directly from Properties. */
+ public static UnlocalizedNumberFormatter create(Properties properties, DecimalFormatSymbols symbols) {
+ MacroProps macros = oldToNew(properties, symbols, null);
+ return NumberFormatter.with().macros(macros);
+ }
+
+ /**
+ * Convenience method to create a NumberFormatter directly from a pattern string. Something like this could become
+ * public API if there is demand.
+ */
+ public static UnlocalizedNumberFormatter create(String pattern, DecimalFormatSymbols symbols) {
+ Properties properties = PatternString.parseToProperties(pattern);
+ return create(properties, symbols);
+ }
+
+ /**
+ * 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 != null;
+ 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 != -1 || maxFrac != -1;
+ boolean explicitMinMaxSig = minSig != -1 || maxSig != -1;
+ // 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 ? -1 : 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 ? -1 : maxInt < minInt ? minInt : maxInt;
+ }
+ Rounder rounding = null;
+ if (explicitCurrencyUsage) {
+ rounding = Rounder.constructCurrency(currencyUsage).withCurrency(currency);
+ } else if (roundingIncrement != null) {
+ rounding = Rounder.constructIncrement(roundingIncrement);
+ } else if (explicitMinMaxSig) {
+ minSig = minSig < 1 ? 1 : minSig > 1000 ? 1000 : minSig;
+ maxSig = maxSig < 0 ? 1000 : maxSig < minSig ? minSig : maxSig > 1000 ? 1000 : maxSig;
+ rounding = Rounder.constructSignificant(minSig, maxSig);
+ } else if (explicitMinMaxFrac) {
+ rounding = Rounder.constructFraction(minFrac, maxFrac);
+ } else if (useCurrency) {
+ rounding = Rounder.constructCurrency(currencyUsage);
+ }
+ if (rounding != null) {
+ rounding = rounding.withMode(mathContext);
+ macros.rounder = 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.grouper = Grouper.getInstance((byte) grouping1, (byte) grouping2, minGrouping == 2);
+
+ /////////////
+ // PADDING //
+ /////////////
+
+ if (properties.getFormatWidth() != -1) {
+ macros.padder = new Padder(properties.getPadString(), properties.getFormatWidth(),
+ properties.getPadPosition());
+ }
+
+ ///////////////////////////////
+ // DECIMAL MARK ALWAYS SHOWN //
+ ///////////////////////////////
+
+ macros.decimal = properties.getDecimalSeparatorAlwaysShown() ? DecimalMarkDisplay.ALWAYS
+ : DecimalMarkDisplay.AUTO;
+
+ ///////////////////////
+ // SIGN ALWAYS SHOWN //
+ ///////////////////////
+
+ macros.sign = properties.getSignAlwaysShown() ? SignDisplay.ALWAYS : SignDisplay.AUTO;
+
+ /////////////////////////
+ // SCIENTIFIC NOTATION //
+ /////////////////////////
+
+ if (properties.getMinimumExponentDigits() != -1) {
+ // 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 != -1) ? maxInt : properties.getMaximumIntegerDigits();
+ engineering = (engineering < 0) ? 0 : (engineering > 8) ? minInt : engineering;
+ // Bug #13289: if maxInt > minInt > 1, then minInt should be 1.
+ // Clear out IntegerWidth to prevent padding extra zeros.
+ if (maxInt > minInt && minInt > 1) {
+ macros.integerWidth = null;
+ }
+ macros.notation = new ScientificNotation(
+ // 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 : SignDisplay.AUTO);
+ // Scientific notation also involves overriding the rounding mode.
+ // TODO: Overriding here is a bit of a hack. Should this logic go earlier?
+ if (macros.rounder instanceof FractionRounder) {
+ 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.rounder = Rounder.constructInfinite().withMode(mathContext);
+ } else if (minInt_ == 0 && minFrac_ == 0) {
+ // Patterns like "#.##E0" (no zeros in the mantissa), which mean round to maxFrac+1
+ macros.rounder = Rounder.constructSignificant(1, maxFrac_ + 1).withMode(mathContext);
+ } else {
+ // All other scientific patterns, which mean round to minInt+maxFrac
+ macros.rounder = Rounder.constructSignificant(minInt + minFrac, minInt + maxFrac)
+ .withMode(mathContext);
+ }
+ }
+ }
+
+ //////////////////////
+ // COMPACT NOTATION //
+ //////////////////////
+
+ if (properties.getCompactStyle() != null) {
+ if (properties.getCompactCustomData() != null) {
+ macros.notation = new CompactNotation(properties.getCompactCustomData());
+ } else if (properties.getCompactStyle() == CompactStyle.LONG) {
+ macros.notation = Notation.compactLong();
+ } else {
+ macros.notation = Notation.compactShort();
+ }
+ // Do not forward the affix provider.
+ macros.affixProvider = null;
+ }
+
+ /////////////////
+ // MULTIPLIERS //
+ /////////////////
+
+ if (properties.getMagnitudeMultiplier() != 0) {
+ macros.multiplier = new MultiplierImpl(properties.getMagnitudeMultiplier());
+ } else if (properties.getMultiplier() != null) {
+ macros.multiplier = new MultiplierImpl(properties.getMultiplier());
+ }
+
+ //////////////////////
+ // PROPERTY EXPORTS //
+ //////////////////////
+
+ if (exportedProperties != null) {
+
+ exportedProperties.setMathContext(mathContext);
+ exportedProperties.setRoundingMode(mathContext.getRoundingMode());
+ exportedProperties.setMinimumIntegerDigits(minInt);
+ exportedProperties.setMaximumIntegerDigits(maxInt == -1 ? Integer.MAX_VALUE : maxInt);
+
+ Rounder rounding_;
+ if (rounding instanceof CurrencyRounder) {
+ rounding_ = ((CurrencyRounder) rounding).withCurrency(currency);
+ } else {
+ rounding_ = rounding;
+ }
+ int minFrac_ = minFrac;
+ int maxFrac_ = maxFrac;
+ int minSig_ = minSig;
+ int maxSig_ = maxSig;
+ BigDecimal increment_ = null;
+ if (rounding_ instanceof FractionRounderImpl) {
+ minFrac_ = ((FractionRounderImpl) rounding_).minFrac;
+ maxFrac_ = ((FractionRounderImpl) rounding_).maxFrac;
+ } else if (rounding_ instanceof IncrementRounderImpl) {
+ increment_ = ((IncrementRounderImpl) rounding_).increment;
+ minFrac_ = increment_.scale();
+ maxFrac_ = increment_.scale();
+ } else if (rounding_ instanceof SignificantRounderImpl) {
+ minSig_ = ((SignificantRounderImpl) rounding_).minSig;
+ maxSig_ = ((SignificantRounderImpl) 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);
+ }
+ }
+}
--- /dev/null
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package newapi;
+
+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.RoundingUtils;
+import com.ibm.icu.util.Currency;
+import com.ibm.icu.util.Currency.CurrencyUsage;
+
+import newapi.impl.MultiplierProducer;
+
+public abstract class Rounder implements Cloneable {
+
+ // FIXME
+ /** @internal */
+ public static final int MAX_VALUE = 100;
+
+ /* package-private final */ MathContext mathContext;
+
+ /* package-private */ Rounder() {
+ mathContext = RoundingUtils.mathContextUnlimited(RoundingMode.HALF_EVEN);
+ }
+
+ public static Rounder none() {
+ return constructInfinite();
+ }
+
+ public static FractionRounder integer() {
+ return constructFraction(0, 0);
+ }
+
+ public static FractionRounder fixedFraction(int minMaxFrac) {
+ if (minMaxFrac >= 0 && minMaxFrac <= MAX_VALUE) {
+ return constructFraction(minMaxFrac, minMaxFrac);
+ } else {
+ throw new IllegalArgumentException("Fraction length must be between 0 and " + MAX_VALUE);
+ }
+ }
+
+ public static FractionRounder minFraction(int minFrac) {
+ if (minFrac >= 0 && minFrac < MAX_VALUE) {
+ return constructFraction(minFrac, -1);
+ } else {
+ throw new IllegalArgumentException("Fraction length must be between 0 and " + MAX_VALUE);
+ }
+ }
+
+ public static FractionRounder maxFraction(int maxFrac) {
+ if (maxFrac >= 0 && maxFrac < MAX_VALUE) {
+ return constructFraction(0, maxFrac);
+ } else {
+ throw new IllegalArgumentException("Fraction length must be between 0 and " + MAX_VALUE);
+ }
+ }
+
+ public static FractionRounder minMaxFraction(int minFrac, int maxFrac) {
+ if (minFrac >= 0 && maxFrac <= MAX_VALUE && minFrac <= maxFrac) {
+ return constructFraction(minFrac, maxFrac);
+ } else {
+ throw new IllegalArgumentException("Fraction length must be between 0 and " + MAX_VALUE);
+ }
+ }
+
+ public static Rounder fixedFigures(int minMaxSig) {
+ if (minMaxSig > 0 && minMaxSig <= MAX_VALUE) {
+ return constructSignificant(minMaxSig, minMaxSig);
+ } else {
+ throw new IllegalArgumentException("Significant digits must be between 0 and " + MAX_VALUE);
+ }
+ }
+
+ public static Rounder minFigures(int minSig) {
+ if (minSig > 0 && minSig <= MAX_VALUE) {
+ return constructSignificant(minSig, -1);
+ } else {
+ throw new IllegalArgumentException("Significant digits must be between 0 and " + MAX_VALUE);
+ }
+ }
+
+ public static Rounder maxFigures(int maxSig) {
+ if (maxSig > 0 && maxSig <= MAX_VALUE) {
+ return constructSignificant(0, maxSig);
+ } else {
+ throw new IllegalArgumentException("Significant digits must be between 0 and " + MAX_VALUE);
+ }
+ }
+
+ public static Rounder minMaxFigures(int minSig, int maxSig) {
+ if (minSig > 0 && maxSig <= MAX_VALUE && minSig <= maxSig) {
+ return constructSignificant(minSig, maxSig);
+ } else {
+ throw new IllegalArgumentException("Significant digits must be between 0 and " + MAX_VALUE);
+ }
+ }
+
+ public static Rounder increment(BigDecimal roundingIncrement) {
+ if (roundingIncrement != null && roundingIncrement.compareTo(BigDecimal.ZERO) > 0) {
+ return constructIncrement(roundingIncrement);
+ } else {
+ throw new IllegalArgumentException("Rounding increment must be positive and non-null");
+ }
+ }
+
+ public static CurrencyRounder currency(CurrencyUsage currencyUsage) {
+ if (currencyUsage != null) {
+ return constructCurrency(currencyUsage);
+ } else {
+ throw new IllegalArgumentException("CurrencyUsage must be non-null");
+ }
+ }
+
+ /**
+ * 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 Rounder withMode(RoundingMode roundingMode) {
+ return withMode(RoundingUtils.mathContextUnlimited(roundingMode));
+ }
+
+ /**
+ * Sets a MathContext directly instead of RoundingMode.
+ *
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
+ public Rounder withMode(MathContext mathContext) {
+ if (this.mathContext.equals(mathContext)) {
+ return this;
+ }
+ Rounder other = (Rounder) this.clone();
+ other.mathContext = mathContext;
+ return other;
+ }
+
+ @Override
+ public Object clone() {
+ try {
+ return super.clone();
+ } catch (CloneNotSupportedException e) {
+ // Should not happen since parent is Object
+ throw new AssertionError(e);
+ }
+ }
+
+ //////////////////////////
+ // PACKAGE-PRIVATE APIS //
+ //////////////////////////
+
+ private static final InfiniteRounderImpl NONE = new InfiniteRounderImpl();
+
+ private static final FractionRounderImpl FIXED_FRAC_0 = new FractionRounderImpl(0, 0);
+ private static final FractionRounderImpl FIXED_FRAC_2 = new FractionRounderImpl(2, 2);
+
+ private static final SignificantRounderImpl FIXED_SIG_2 = new SignificantRounderImpl(2, 2);
+ private static final SignificantRounderImpl FIXED_SIG_3 = new SignificantRounderImpl(3, 3);
+ private static final SignificantRounderImpl RANGE_SIG_2_3 = new SignificantRounderImpl(2, 3);
+
+ /* package-private */ static final FracSigRounderImpl COMPACT_STRATEGY = new FracSigRounderImpl(0, 0, 2, -1);
+
+ private static final IncrementRounderImpl NICKEL = new IncrementRounderImpl(BigDecimal.valueOf(0.5));
+
+ private static final CurrencyRounderImpl MONETARY_STANDARD = new CurrencyRounderImpl(CurrencyUsage.STANDARD);
+ private static final CurrencyRounderImpl MONETARY_CASH = new CurrencyRounderImpl(CurrencyUsage.CASH);
+
+ private static final PassThroughRounderImpl PASS_THROUGH = new PassThroughRounderImpl();
+
+ static Rounder constructInfinite() {
+ return NONE;
+ }
+
+ static FractionRounder constructFraction(int minFrac, int maxFrac) {
+ if (minFrac == 0 && maxFrac == 0) {
+ return FIXED_FRAC_0;
+ } else if (minFrac == 2 && maxFrac == 2) {
+ return FIXED_FRAC_2;
+ } else {
+ return new FractionRounderImpl(minFrac, maxFrac);
+ }
+ }
+
+ /** Assumes that minSig <= maxSig. */
+ static Rounder constructSignificant(int minSig, int maxSig) {
+ if (minSig == 2 && maxSig == 2) {
+ return FIXED_SIG_2;
+ } else if (minSig == 3 && maxSig == 3) {
+ return FIXED_SIG_3;
+ } else if (minSig == 2 && maxSig == 3) {
+ return RANGE_SIG_2_3;
+ } else {
+ return new SignificantRounderImpl(minSig, maxSig);
+ }
+ }
+
+ static Rounder constructFractionSignificant(FractionRounder base_, int minSig, int maxSig) {
+ assert base_ instanceof FractionRounderImpl;
+ FractionRounderImpl base = (FractionRounderImpl) base_;
+ if (base.minFrac == 0 && base.maxFrac == 0 && minSig == 2 /* && maxSig == -1 */) {
+ return COMPACT_STRATEGY;
+ } else {
+ return new FracSigRounderImpl(base.minFrac, base.maxFrac, minSig, maxSig);
+ }
+ }
+
+ static Rounder constructIncrement(BigDecimal increment) {
+ if (increment.compareTo(NICKEL.increment) == 0) {
+ return NICKEL;
+ } else {
+ return new IncrementRounderImpl(increment);
+ }
+ }
+
+ static CurrencyRounder constructCurrency(CurrencyUsage usage) {
+ if (usage == CurrencyUsage.STANDARD) {
+ return MONETARY_STANDARD;
+ } else if (usage == CurrencyUsage.CASH) {
+ return MONETARY_CASH;
+ } else {
+ throw new AssertionError();
+ }
+ }
+
+ static Rounder constructFromCurrency(CurrencyRounder base_, Currency currency) {
+ assert base_ instanceof CurrencyRounderImpl;
+ CurrencyRounderImpl base = (CurrencyRounderImpl) base_;
+ double incrementDouble = currency.getRoundingIncrement(base.usage);
+ if (incrementDouble != 0.0) {
+ BigDecimal increment = BigDecimal.valueOf(incrementDouble);
+ return constructIncrement(increment);
+ } else {
+ int minMaxFrac = currency.getDefaultFractionDigits(base.usage);
+ return constructFraction(minMaxFrac, minMaxFrac);
+ }
+ }
+
+ static Rounder constructPassThrough() {
+ return PASS_THROUGH;
+ }
+
+ /**
+ * Returns a valid working Rounder. If the Rounder is a CurrencyRounder, applies the given currency. Otherwise,
+ * simply passes through the argument.
+ *
+ * @param rounder
+ * The input object.
+ * @param currency
+ * A currency object to use in case the input object needs it.
+ * @return A Rounder object ready for use.
+ */
+ static Rounder normalizeType(Rounder rounder, Currency currency) {
+ if (rounder instanceof CurrencyRounder) {
+ return ((CurrencyRounder) rounder).withCurrency(currency);
+ } else {
+ return rounder;
+ }
+ }
+
+ abstract void apply(FormatQuantity value);
+
+ 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;
+ }
+
+ ///////////////
+ // INTERNALS //
+ ///////////////
+
+ static class InfiniteRounderImpl extends Rounder {
+
+ private InfiniteRounderImpl() {
+ }
+
+ @Override
+ void apply(FormatQuantity value) {
+ value.roundToInfinity();
+ value.setFractionLength(0, Integer.MAX_VALUE);
+ }
+ }
+
+ static class FractionRounderImpl extends FractionRounder {
+ final int minFrac;
+ final int maxFrac;
+
+ private FractionRounderImpl(int minFrac, int maxFrac) {
+ this.minFrac = minFrac;
+ this.maxFrac = maxFrac;
+ }
+
+ @Override
+ void apply(FormatQuantity value) {
+ value.roundToMagnitude(getRoundingMagnitudeFraction(maxFrac), mathContext);
+ value.setFractionLength(Math.max(0, -getDisplayMagnitudeFraction(minFrac)), Integer.MAX_VALUE);
+ }
+ }
+
+ static class SignificantRounderImpl extends Rounder {
+ final int minSig;
+ final int maxSig;
+
+ private SignificantRounderImpl(int minSig, int maxSig) {
+ this.minSig = minSig;
+ this.maxSig = maxSig;
+ }
+
+ @Override
+ void apply(FormatQuantity value) {
+ value.roundToMagnitude(getRoundingMagnitudeSignificant(value, maxSig), mathContext);
+ value.setFractionLength(Math.max(0, -getDisplayMagnitudeSignificant(value, minSig)), Integer.MAX_VALUE);
+ }
+
+ /** Version of {@link #apply} that obeys minInt constraints. Used for scientific notation compatibility mode. */
+ public void apply(FormatQuantity quantity, int minInt) {
+ assert quantity.isZero();
+ quantity.setFractionLength(minSig - minInt, Integer.MAX_VALUE);
+ }
+ }
+
+ static class FracSigRounderImpl extends Rounder {
+ final int minFrac;
+ final int maxFrac;
+ final int minSig;
+ final int maxSig;
+
+ private FracSigRounderImpl(int minFrac, int maxFrac, int minSig, int maxSig) {
+ this.minFrac = minFrac;
+ this.maxFrac = maxFrac;
+ this.minSig = minSig;
+ this.maxSig = maxSig;
+ }
+
+ @Override
+ void apply(FormatQuantity value) {
+ int displayMag = getDisplayMagnitudeFraction(minFrac);
+ int roundingMag = getRoundingMagnitudeFraction(maxFrac);
+ if (minSig == -1) {
+ // Max Sig override
+ int candidate = getRoundingMagnitudeSignificant(value, maxSig);
+ roundingMag = Math.max(roundingMag, candidate);
+ } else {
+ // Min Sig override
+ int candidate = getDisplayMagnitudeSignificant(value, minSig);
+ roundingMag = Math.min(roundingMag, candidate);
+ }
+ value.roundToMagnitude(roundingMag, mathContext);
+ value.setFractionLength(Math.max(0, -displayMag), Integer.MAX_VALUE);
+ }
+ }
+
+ static class IncrementRounderImpl extends Rounder {
+ final BigDecimal increment;
+
+ private IncrementRounderImpl(BigDecimal increment) {
+ this.increment = increment;
+ }
+
+ @Override
+ void apply(FormatQuantity value) {
+ value.roundToIncrement(increment, mathContext);
+ value.setFractionLength(increment.scale(), increment.scale());
+ }
+ }
+
+ static class CurrencyRounderImpl extends CurrencyRounder {
+ final CurrencyUsage usage;
+
+ private CurrencyRounderImpl(CurrencyUsage usage) {
+ this.usage = usage;
+ }
+
+ @Override
+ void apply(FormatQuantity value) {
+ // Call .withCurrency() before .apply()!
+ throw new AssertionError();
+ }
+ }
+
+ static class PassThroughRounderImpl extends Rounder {
+
+ private PassThroughRounderImpl() {
+ }
+
+ @Override
+ void apply(FormatQuantity value) {
+ // TODO: Assert that value has already been rounded
+ }
+ }
+
+ private static int getRoundingMagnitudeFraction(int maxFrac) {
+ if (maxFrac == -1) {
+ return Integer.MIN_VALUE;
+ }
+ return -maxFrac;
+ }
+
+ private static int getRoundingMagnitudeSignificant(FormatQuantity value, int maxSig) {
+ if (maxSig == -1) {
+ return Integer.MIN_VALUE;
+ }
+ int magnitude = value.isZero() ? 0 : value.getMagnitude();
+ return magnitude - maxSig + 1;
+ }
+
+ private static int getDisplayMagnitudeFraction(int minFrac) {
+ if (minFrac == 0) {
+ return Integer.MAX_VALUE;
+ }
+ return -minFrac;
+ }
+
+ private static int getDisplayMagnitudeSignificant(FormatQuantity value, int minSig) {
+ int magnitude = value.isZero() ? 0 : value.getMagnitude();
+ return magnitude - minSig + 1;
+ }
+}
--- /dev/null
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package newapi;
+
+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.Rounder.SignificantRounderImpl;
+import newapi.impl.MicroProps;
+import newapi.impl.MultiplierProducer;
+import newapi.impl.QuantityChain;
+
+@SuppressWarnings("unused")
+public class ScientificNotation extends Notation implements Cloneable {
+
+ int engineeringInterval;
+ boolean requireMinInt;
+ int minExponentDigits;
+ SignDisplay exponentSignDisplay;
+
+ /* package-private */ ScientificNotation(int engineeringInterval, boolean requireMinInt, int minExponentDigits,
+ SignDisplay exponentSignDisplay) {
+ this.engineeringInterval = engineeringInterval;
+ this.requireMinInt = requireMinInt;
+ this.minExponentDigits = minExponentDigits;
+ this.exponentSignDisplay = exponentSignDisplay;
+ }
+
+ public ScientificNotation withMinExponentDigits(int minExponentDigits) {
+ if (minExponentDigits >= 0 && minExponentDigits < Rounder.MAX_VALUE) {
+ ScientificNotation other = (ScientificNotation) this.clone();
+ other.minExponentDigits = minExponentDigits;
+ return other;
+ } else {
+ throw new IllegalArgumentException("Integer digits must be between 0 and " + Rounder.MAX_VALUE);
+ }
+ }
+
+ public ScientificNotation withExponentSignDisplay(SignDisplay exponentSignDisplay) {
+ ScientificNotation other = (ScientificNotation) 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);
+ }
+ }
+
+ /* package-private */ QuantityChain withLocaleData(DecimalFormatSymbols symbols, boolean build,
+ QuantityChain parent) {
+ return new MurkyScientificHandler(symbols, build, parent);
+ }
+
+ private class MurkyScientificHandler implements QuantityChain, MultiplierProducer, Modifier {
+
+ final DecimalFormatSymbols symbols;
+ final ImmutableScientificModifier[] precomputedMods;
+ final QuantityChain parent;
+ /* unsafe */ int exponent;
+
+ private MurkyScientificHandler(DecimalFormatSymbols symbols, boolean safe, QuantityChain parent) {
+ this.symbols = symbols;
+ this.parent = parent;
+
+ if (safe) {
+ // Pre-build the modifiers for exponents -12 through 12
+ precomputedMods = new ImmutableScientificModifier[25];
+ for (int i = -12; i <= 12; i++) {
+ precomputedMods[i + 12] = new ImmutableScientificModifier(i);
+ }
+ } else {
+ precomputedMods = null;
+ }
+ }
+
+ @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 (requireMinInt && micros.rounding instanceof SignificantRounderImpl) {
+ // Show "00.000E0" on pattern "00.000E0"
+ ((SignificantRounderImpl) micros.rounding).apply(quantity, 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) {
+ // Safe code path A
+ micros.modInner = precomputedMods[exponent + 12];
+ } else if (precomputedMods != null) {
+ // Safe code path B
+ micros.modInner = new ImmutableScientificModifier(exponent);
+ } else {
+ // Unsafe code path: mutates the object and re-uses it as a Modifier!
+ this.exponent = exponent;
+ micros.modInner = this;
+ }
+
+ // We already performed rounding. Do not perform it again.
+ micros.rounding = Rounder.constructPassThrough();
+
+ return micros;
+ }
+
+ @Override
+ public int getMultiplier(int magnitude) {
+ int interval = engineeringInterval;
+ int digitsShown;
+ if (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;
+ }
+
+ @Override
+ public boolean isStrong() {
+ return true;
+ }
+
+ @Override
+ public String getPrefix() {
+ // FIXME: Localized exponent separator location.
+ return "";
+ }
+
+ @Override
+ public String getSuffix() {
+ // FIXME: Localized exponent separator location.
+ NumberStringBuilder temp = new NumberStringBuilder();
+ doApply(exponent, temp, 0);
+ return temp.toString();
+ }
+
+ @Override
+ public int apply(NumberStringBuilder output, int leftIndex, int rightIndex) {
+ return doApply(exponent, output, rightIndex);
+ }
+
+ private int doApply(int exponent, NumberStringBuilder output, 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 && exponentSignDisplay != SignDisplay.NEVER) {
+ i += output.insert(i, symbols.getMinusSignString(), NumberFormat.Field.EXPONENT_SIGN);
+ } else if (exponentSignDisplay == SignDisplay.ALWAYS) {
+ 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 < 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;
+ }
+
+ private class ImmutableScientificModifier implements Modifier {
+ final int exponent;
+
+ ImmutableScientificModifier(int exponent) {
+ this.exponent = exponent;
+ }
+
+ @Override
+ public boolean isStrong() {
+ return true;
+ }
+
+ @Override
+ public String getPrefix() {
+ // FIXME: Localized exponent separator location.
+ return "";
+ }
+
+ @Override
+ public String getSuffix() {
+ // FIXME: Localized exponent separator location.
+ NumberStringBuilder temp = new NumberStringBuilder();
+ doApply(exponent, temp, 0);
+ return temp.toString();
+ }
+
+ @Override
+ public int apply(NumberStringBuilder output, int leftIndex, int rightIndex) {
+ return doApply(exponent, output, rightIndex);
+ }
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package newapi;
+
+public class SimpleNotation extends Notation {
+ /* package-private */ SimpleNotation() {
+ }
+}
\ No newline at end of file
--- /dev/null
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package newapi;
+
+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.SignDisplay;
+import newapi.Rounder.CurrencyRounderImpl;
+import newapi.Rounder.FracSigRounderImpl;
+import newapi.Rounder.FractionRounderImpl;
+import newapi.Rounder.IncrementRounderImpl;
+import newapi.Rounder.InfiniteRounderImpl;
+import newapi.Rounder.SignificantRounderImpl;
+import newapi.impl.MacroProps;
+
+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.rounder != null) {
+ // sb.append("rounding=");
+ rounderToSkeleton(macros.rounder, sb);
+ sb.append(' ');
+ }
+ if (macros.grouper != null) {
+ sb.append("grouping=");
+ grouperToSkeleton(macros.grouper, sb);
+ sb.append(' ');
+ }
+// if (macros.padder != null) {
+// sb.append("padding=");
+// paddingToSkeleton(macros.padder, 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 ScientificNotation) {
+ ScientificNotation notation = (ScientificNotation) value;
+ sb.append('E');
+ if (notation.engineeringInterval != 1) {
+ sb.append(notation.engineeringInterval);
+ }
+ if (notation.exponentSignDisplay == SignDisplay.ALWAYS) {
+ sb.append('+');
+ } else if (notation.exponentSignDisplay == SignDisplay.NEVER) {
+ 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 CompactNotation) {
+ CompactNotation notation = (CompactNotation) value;
+ if (notation.compactStyle == CompactStyle.SHORT) {
+ sb.append('C');
+ } else {
+ // FIXME: CCC or CCCC instead?
+ sb.append("CC");
+ }
+ } else {
+ assert value instanceof SimpleNotation;
+ 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;
+ c = safeCharAt(skeleton, offset++);
+ }
+ if (c == '!') {
+ sign = SignDisplay.NEVER;
+ c = safeCharAt(skeleton, offset++);
+ }
+ while (c == '0') {
+ minExponentDigits++;
+ c = safeCharAt(skeleton, offset++);
+ }
+ minExponentDigits = Math.max(1, minExponentDigits);
+ result = new ScientificNotation(engineering, false, minExponentDigits, sign);
+ } else if (c0 == 'C') {
+ char c = safeCharAt(skeleton, offset++);
+ if (c == 'C') {
+ result = Notation.compactLong();
+ } else {
+ result = Notation.compactShort();
+ }
+ } 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 rounderToSkeleton(Rounder value, StringBuilder sb) {
+ if (!(value instanceof Rounder)) {
+ // FIXME: Throw an exception here instead?
+ return;
+ }
+ MathContext mathContext;
+ if (value instanceof FractionRounderImpl) {
+ FractionRounderImpl rounder = (FractionRounderImpl) value;
+ sb.append('F');
+ minMaxToSkeletonHelper(rounder.minFrac, rounder.maxFrac, sb);
+ mathContext = rounder.mathContext;
+ } else if (value instanceof SignificantRounderImpl) {
+ SignificantRounderImpl rounder = (SignificantRounderImpl) value;
+ sb.append('S');
+ minMaxToSkeletonHelper(rounder.minSig, rounder.maxSig, sb);
+ mathContext = rounder.mathContext;
+ } else if (value instanceof FracSigRounderImpl) {
+ FracSigRounderImpl rounder = (FracSigRounderImpl) value;
+ sb.append('F');
+ minMaxToSkeletonHelper(rounder.minFrac, rounder.maxFrac, sb);
+ if (rounder.minSig != -1) {
+ sb.append('>');
+ sb.append(rounder.minSig);
+ } else {
+ sb.append('<');
+ sb.append(rounder.maxSig);
+ }
+ mathContext = rounder.mathContext;
+ } else if (value instanceof IncrementRounderImpl) {
+ IncrementRounderImpl rounder = (IncrementRounderImpl) value;
+ sb.append('M');
+ sb.append(rounder.increment.toString());
+ mathContext = rounder.mathContext;
+ } else if (value instanceof CurrencyRounderImpl) {
+ CurrencyRounderImpl rounder = (CurrencyRounderImpl) value;
+ sb.append('G');
+ sb.append(rounder.usage.name());
+ mathContext = rounder.mathContext;
+ } else {
+ InfiniteRounderImpl rounder = (InfiniteRounderImpl) value;
+ sb.append('Y');
+ mathContext = rounder.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++);
+ Rounder result = null;
+ if (c0 == 'F') {
+ int[] minMax = new int[2];
+ offset += skeletonToMinMaxHelper(skeleton, offset, minMax);
+ FractionRounder temp = Rounder.constructFraction(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 = Rounder.constructSignificant(minMax[0], minMax[1]);
+ } else if (c0 == 'M') {
+ StringBuilder sb = new StringBuilder();
+ offset += consumeUntil(skeleton, offset, ' ', sb);
+ BigDecimal increment = new BigDecimal(sb.toString());
+ result = Rounder.constructIncrement(increment);
+ } else if (c0 == 'G') {
+ StringBuilder sb = new StringBuilder();
+ offset += consumeUntil(skeleton, offset, ' ', sb);
+ CurrencyUsage usage = Enum.valueOf(CurrencyUsage.class, sb.toString());
+ result = Rounder.constructCurrency(usage);
+ } else if (c0 == 'Y') {
+ result = Rounder.constructInfinite();
+ }
+ output.rounder = 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 grouperToSkeleton(Grouper value, StringBuilder sb) {
+ if (value.equals(Grouper.defaults())) {
+ sb.append("defaults");
+ } else if (value.equals(Grouper.min2())) {
+ sb.append("min2");
+ } else if (value.equals(Grouper.none())) {
+ sb.append("none");
+ } else {
+ // Not supported in skeleton string
+ sb.append("defaults");
+ }
+ }
+
+ private static int skeletonToGrouping(String skeleton, int offset, MacroProps output) {
+ int originalOffset = offset;
+ char c0 = skeleton.charAt(offset++);
+ Grouper result = null;
+ StringBuilder sb = new StringBuilder();
+ offset += consumeUntil(skeleton, --offset, ' ', sb);
+ String name = sb.toString();
+ if (name.equals("defaults")) {
+ result = Grouper.defaults();
+ } else if (name.equals("min2")) {
+ result = Grouper.min2();
+ } else if (name.equals("none")) {
+ result = Grouper.none();
+ }
+ output.grouper = result;
+ return offset - originalOffset;
+ }
+
+// private static void paddingToSkeleton(Padder value, StringBuilder sb) {
+// PaddingImpl padding = (PaddingImpl) value;
+// if (padding == Padder.NONE) {
+// sb.append("NONE");
+// return;
+// }
+// sb.append(padding.targetWidth);
+// sb.append(':');
+// sb.append(padding.position.name());
+// sb.append(':');
+// if (!padding.paddingString.equals(" ")) {
+// sb.append(padding.paddingString);
+// }
+// }
+//
+// private static int skeletonToPadding(String skeleton, int offset, MacroProps output) {
+// int originalOffset = offset;
+// char c0 = skeleton.charAt(offset++);
+// if (c0 == 'N') {
+// offset += consumeUntil(skeleton, --offset, ' ', null);
+// } else if ('0' <= c0 && c0 <= '9') {
+// long intResult = consumeInt(skeleton, --offset);
+// offset += intResult & 0xffffffff;
+// int width = (int) (intResult >>> 32);
+// char c1 = safeCharAt(skeleton, offset++);
+// if (c1 != ':') {
+// return offset - originalOffset - 1;
+// }
+// StringBuilder sb = new StringBuilder();
+// offset += consumeUntil(skeleton, offset, ':', sb);
+// String padPositionString = sb.toString();
+// sb.setLength(0);
+// offset += consumeUntil(skeleton, offset, ' ', sb);
+// String string = (sb.length() == 0) ? " " : sb.toString();
+// PadPosition position = Enum.valueOf(PadPosition.class, padPositionString);
+// output.padder = PaddingImpl.getInstance(string, width, position);
+// }
+// return offset - originalOffset;
+// }
+
+ private static void integerWidthToSkeleton(IntegerWidth value, StringBuilder sb) {
+ sb.append(value.minInt);
+ if (value.maxInt != value.minInt) {
+ sb.append('-');
+ if (value.maxInt != -1) {
+ sb.append(value.maxInt);
+ }
+ }
+ }
+
+ private static int skeletonToIntegerWidth(String skeleton, int offset, MacroProps output) {
+ int originalOffset = offset;
+ long intResult = consumeInt(skeleton, offset);
+ offset += intResult & 0xffffffff;
+ int minInt = (int) (intResult >>> 32);
+ char c1 = safeCharAt(skeleton, --offset);
+ int maxInt;
+ if (c1 == '-') {
+ intResult = consumeInt(skeleton, offset);
+ offset += intResult & 0xffffffff;
+ maxInt = (int) (intResult >>> 32);
+ }
+ }
+
+ 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 int skeletonToUnitWidth(String skeleton, int offset, MacroProps output) {
+ int originalOffset = offset;
+ StringBuilder sb = new StringBuilder();
+ offset += consumeUntil(skeleton, offset, ' ', sb);
+ output.unitWidth = Enum.valueOf(FormatWidth.class, sb.toString());
+ return offset - originalOffset;
+ }
+
+ private static void signToSkeleton(SignDisplay value, StringBuilder sb) {
+ sb.append(value.name());
+ }
+
+ private static int skeletonToSign(String skeleton, int offset, MacroProps output) {
+ int originalOffset = offset;
+ StringBuilder sb = new StringBuilder();
+ offset += consumeUntil(skeleton, offset, ' ', sb);
+ output.sign = Enum.valueOf(SignDisplay.class, sb.toString());
+ return offset - originalOffset;
+ }
+
+ private static void decimalToSkeleton(DecimalMarkDisplay value, StringBuilder sb) {
+ sb.append(value.name());
+ }
+
+ private static int skeletonToDecimal(String skeleton, int offset, MacroProps output) {
+ int originalOffset = offset;
+ StringBuilder sb = new StringBuilder();
+ offset += consumeUntil(skeleton, offset, ' ', sb);
+ output.decimal = Enum.valueOf(DecimalMarkDisplay.class, sb.toString());
+ return offset - originalOffset;
+ }
+
+ 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) {
+ if (sb != null)
+ sb.append(c);
+ c = safeCharAt(skeleton, offset++);
+ }
+ return offset - originalOffset;
+ }
+
+ private static long consumeInt(String skeleton, int offset) {
+ int originalOffset = offset;
+ char c = safeCharAt(skeleton, offset++);
+ int result = 0;
+ while ('0' <= c && c <= '9') {
+ result = (result * 10) + (c - '0');
+ c = safeCharAt(skeleton, offset++);
+ }
+ return (offset - originalOffset) | (((long) result) << 32);
+ }
+}
--- /dev/null
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package newapi;
+
+import java.util.Locale;
+
+import com.ibm.icu.util.ULocale;
+
+public class UnlocalizedNumberFormatter extends NumberFormatterSettings<UnlocalizedNumberFormatter> {
+
+ /** Base constructor; called during startup only. Sets the threshold to the default value of 3. */
+ UnlocalizedNumberFormatter() {
+ super(null, KEY_THRESHOLD, new Long(3));
+ }
+
+ UnlocalizedNumberFormatter(NumberFormatterSettings<?> parent, int key, Object value) {
+ super(parent, key, value);
+ }
+
+ public LocalizedNumberFormatter locale(Locale locale) {
+ return new LocalizedNumberFormatter(this, KEY_LOCALE, ULocale.forLocale(locale));
+ }
+
+ public LocalizedNumberFormatter locale(ULocale locale) {
+ return new LocalizedNumberFormatter(this, KEY_LOCALE, locale);
+ }
+
+ @Override
+ protected UnlocalizedNumberFormatter create(int key, Object value) {
+ return new UnlocalizedNumberFormatter(this, key, value);
+ }
+}
\ No newline at end of file
--- /dev/null
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package newapi;
+
+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.NumberStringBuilder;
+import com.ibm.icu.impl.number.modifiers.ConstantAffixModifier;
+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.SignDisplay;
+import newapi.impl.MacroProps;
+import newapi.impl.MicroProps;
+import newapi.impl.Padder;
+import newapi.impl.QuantityChain;
+
+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;
+ MurkyLongNameHandler longNames = null;
+ Rounder defaultRounding = Rounder.none();
+ Currency currency = DEFAULT_CURRENCY;
+ FormatWidth unitWidth = null;
+ boolean perMille = false;
+ PluralRules rules = input.rules;
+
+ MicroProps micros = new MicroProps(build);
+ 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 ? IntegerWidth.zeroFillTo(1) : 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 = Rounder.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);
+ longNames = MurkyLongNameHandler.getCurrencyLongNameModifiers(input.loc, (Currency) input.unit);
+ defaultRounding = Rounder.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;
+ longNames = MurkyLongNameHandler.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) {
+ chain = input.multiplier.copyAndChain(chain);
+ }
+
+ // Rounding strategy
+ if (input.rounder != null) {
+ micros.rounding = Rounder.normalizeType(input.rounder, currency);
+ } else if (input.notation instanceof CompactNotation) {
+ micros.rounding = Rounder.COMPACT_STRATEGY;
+ } else {
+ micros.rounding = Rounder.normalizeType(defaultRounding, currency);
+ }
+
+ // Grouping strategy
+ if (input.grouper != null) {
+ micros.grouping = Grouper.normalizeType(input.grouper, patternInfo);
+ } else if (input.notation instanceof CompactNotation) {
+ // Compact notation uses minGrouping by default since ICU 59
+ micros.grouping = Grouper.normalizeType(Grouper.min2(), patternInfo);
+ } else {
+ micros.grouping = Grouper.normalizeType(Grouper.defaults(), patternInfo);
+ }
+
+ // Inner modifier (scientific notation)
+ if (input.notation instanceof ScientificNotation) {
+ chain = ((ScientificNotation) input.notation).withLocaleData(micros.symbols, build, chain);
+ } else {
+ // No inner modifier required
+ micros.modInner = ConstantAffixModifier.EMPTY;
+ }
+
+ // Middle modifier (patterns, positive/negative, currency symbols, percent)
+ // The default middle modifier is weak (thus the false argument).
+ 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.createImmutableAndChain(chain);
+ } else {
+ chain = murkyMod.addToChain(chain);
+ }
+
+ // Outer modifier (CLDR units and currency long names)
+ if (longNames != null) {
+ if (rules == null) {
+ // Lazily create PluralRules
+ rules = PluralRules.forLocale(input.loc);
+ }
+ chain = longNames.withLocaleData(rules, build, chain);
+ } else {
+ // No outer modifier required
+ micros.modOuter = ConstantAffixModifier.EMPTY;
+ }
+
+ // Padding strategy
+ if (input.padder != null) {
+ micros.padding = input.padder;
+ } else {
+ micros.padding = Padder.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 CompactNotation) {
+ if (rules == null) {
+ // Lazily create PluralRules
+ rules = PluralRules.forLocale(input.loc);
+ }
+ CompactType compactType = (input.unit instanceof Currency) ? CompactType.CURRENCY : CompactType.DECIMAL;
+ chain = ((CompactNotation) input.notation).withLocaleData(input.loc, compactType, rules,
+ build ? murkyMod : null, chain);
+ }
+
+ return chain;
+ }
+
+ //////////
+
+ private static int applyStatic(MicroProps micros, FormatQuantity inValue, NumberStringBuilder outString) {
+ inValue.adjustMagnitude(micros.multiplier);
+ micros.rounding.apply(inValue);
+ if (micros.integerWidth.maxInt == -1) {
+ inValue.setIntegerLength(micros.integerWidth.minInt, Integer.MAX_VALUE);
+ } else {
+ inValue.setIntegerLength(micros.integerWidth.minInt, micros.integerWidth.maxInt);
+ }
+ int length = writeNumber(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;
+ }
+
+ private static int writeNumber(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 += writeIntegerDigits(micros, input, string);
+
+ // Add the decimal point
+ if (input.getLowerDisplayMagnitude() < 0 || micros.decimal == DecimalMarkDisplay.ALWAYS) {
+ length += string.insert(length, micros.useCurrency ? micros.symbols.getMonetaryDecimalSeparatorString()
+ : micros.symbols.getDecimalSeparatorString(), NumberFormat.Field.DECIMAL_SEPARATOR);
+ }
+
+ // Add the fraction digits
+ length += writeFractionDigits(micros, input, string);
+ }
+
+ return length;
+ }
+
+ private static int writeIntegerDigits(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 writeFractionDigits(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;
+ }
+}
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.UResourceBundle;
-class CompactData implements RoundingImpl.MultiplierProducer {
+public class CompactData implements MultiplierProducer {
public static CompactData getInstance(
ULocale locale, CompactType compactType, CompactStyle compactStyle) {
+++ /dev/null
-// © 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,
- MurkyModifier buildReference,
- QuantityChain parent) {
- CompactData data = CompactData.getInstance(dataLocale, compactType, compactStyle);
- return new CompactImpl(data, rules, buildReference, parent);
- }
-
- public static CompactImpl getInstance(
- Map<String, Map<String, String>> compactCustomData,
- PluralRules rules,
- MurkyModifier buildReference,
- QuantityChain parent) {
- CompactData data = CompactData.getInstance(compactCustomData);
- return new CompactImpl(data, rules, buildReference, parent);
- }
-
- private CompactImpl(
- CompactData data, PluralRules rules, MurkyModifier buildReference, QuantityChain parent) {
- this.data = data;
- this.rules = rules;
- if (buildReference != null) {
- precomputedMods = precomputeAllModifiers(data, buildReference);
- } else {
- precomputedMods = null;
- }
- this.parent = parent;
- }
-
- /** To be used by the building code path */
- public static Map<String, CompactModInfo> precomputeAllModifiers(
- CompactData data, MurkyModifier buildReference) {
- Map<String, CompactModInfo> precomputedMods = new HashMap<String, CompactModInfo>();
- Set<String> allPatterns = data.getAllPatterns();
- for (String patternString : allPatterns) {
- CompactModInfo info = new CompactModInfo();
- PatternParseResult patternInfo = LdmlPatternInfo.parse(patternString);
- buildReference.setPatternInfo(patternInfo);
- info.mod = buildReference.createImmutable();
- info.numDigits = patternInfo.positive.totalIntegerDigits;
- precomputedMods.put(patternString, info);
- }
- return precomputedMods;
- }
-
- private static class CompactModInfo {
- public ImmutableMurkyModifier mod;
- public int numDigits;
- }
-
- @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;
- }
-}
+++ /dev/null
-// © 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;
- }
-}
+++ /dev/null
-// © 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 byte B2 = 2;
- private static final byte B3 = 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(B3, B3, false);
- static final GroupingImpl GROUPING_3_2 = new GroupingImpl(B3, B2, false);
- static final GroupingImpl GROUPING_3_MIN2 = new GroupingImpl(B3, B3, true);
- static final GroupingImpl GROUPING_3_2_MIN2 = new GroupingImpl(B3, B2, true);
-
- static GroupingImpl getInstance(byte grouping1, byte 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 byte grouping1; // -2 means "needs locale data"; -1 means "no grouping"
- final byte 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(byte grouping1, byte 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;
- // TODO: short or byte?
- byte grouping1 = (byte) (patternInfo.positive.groupingSizes & 0xffff);
- byte grouping2 = (byte) ((patternInfo.positive.groupingSizes >>> 16) & 0xffff);
- byte grouping3 = (byte) ((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);
- }
-}
+++ /dev/null
-// © 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;
-import newapi.NumberFormatter.Rounding;
-
-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) {
- if (maxInt == this.maxInt) {
- return this;
- } else if (maxInt >= 0 && maxInt < Rounding.MAX_VALUE) {
- return new IntegerWidthImpl(minInt, maxInt);
- } else if (maxInt == Integer.MAX_VALUE) {
- return new IntegerWidthImpl(minInt, maxInt);
- } else {
- throw new IllegalArgumentException(
- "Integer digits must be between 0 and " + Rounding.MAX_VALUE);
- }
- }
-}
import com.ibm.icu.util.MeasureUnit;
import com.ibm.icu.util.ULocale;
+import newapi.Grouper;
+import newapi.IntegerWidth;
+import newapi.Notation;
+import newapi.NumberFormatter;
+import newapi.Rounder;
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 Rounder rounder;
+ public Grouper grouper;
+ public Padder padder;
public IntegerWidth integerWidth;
public Object symbols;
public FormatWidth unitWidth;
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 (rounder == null) rounder = fallback.rounder;
+ if (grouper == null) grouper = fallback.grouper;
+ if (padder == null) padder = fallback.padder;
if (integerWidth == null) integerWidth = fallback.integerWidth;
if (symbols == null) symbols = fallback.symbols;
if (unitWidth == null) unitWidth = fallback.unitWidth;
return Objects.hash(
notation,
unit,
- rounding,
- grouping,
- padding,
+ rounder,
+ grouper,
+ padder,
integerWidth,
symbols,
unitWidth,
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(rounder, other.rounder)
+ && Objects.equals(grouper, other.grouper)
+ && Objects.equals(padder, other.padder)
&& Objects.equals(integerWidth, other.integerWidth)
&& Objects.equals(symbols, other.symbols)
&& Objects.equals(unitWidth, other.unitWidth)
import com.ibm.icu.impl.number.Modifier;
import com.ibm.icu.text.DecimalFormatSymbols;
+import newapi.Grouper;
+import newapi.IntegerWidth;
+import newapi.NumberFormatter;
+import newapi.Rounder;
import newapi.NumberFormatter.DecimalMarkDisplay;
import newapi.NumberFormatter.SignDisplay;
// Populated globally:
public SignDisplay sign;
public DecimalFormatSymbols symbols;
- public PaddingImpl padding;
+ public Padder padding;
public DecimalMarkDisplay decimal;
- public IntegerWidthImpl integerWidth;
+ public IntegerWidth integerWidth;
// Populated by notation/unit:
public Modifier modOuter;
public Modifier modMiddle;
public Modifier modInner;
- public RoundingImpl rounding;
- public GroupingImpl grouping;
+ public Rounder rounding;
+ public Grouper grouping;
public int multiplier;
public boolean useCurrency;
--- /dev/null
+// © 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package newapi.impl;
+
+public interface MultiplierProducer {
+ int getMultiplier(int magnitude);
+}
\ No newline at end of file
+++ /dev/null
-// © 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.LdmlPatternInfo;
-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 class is a {@link Modifier} that wraps a decimal format pattern. It applies the pattern's
- * affixes in {@link Modifier#apply}.
- *
- * <p>In addition to being a Modifier, this class contains the business logic for substituting the
- * correct locale symbols into the affixes of the decimal format pattern.
- *
- * <p>In order to use this class, create a new instance and call the following four setters: {@link
- * #setPatternInfo}, {@link #setPatternAttributes}, {@link #setSymbols}, and {@link
- * #setNumberProperties}. After calling these four setters, the instance will be ready for use as a
- * Modifier.
- *
- * <p>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! Instead, you can obtain a safe, immutable
- * decimal format pattern modifier by calling {@link MurkyModifier#createImmutable}, in effect
- * treating this instance as a builder for the immutable variant.
- */
-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;
-
- /**
- * @param isStrong Whether the modifier should be considered strong. For more information, see
- * {@link Modifier#isStrong()}. Most of the time, decimal format pattern modifiers should be
- * considered as non-strong.
- */
- public MurkyModifier(boolean isStrong) {
- this.isStrong = isStrong;
- }
-
- /**
- * Sets a reference to the parsed decimal format pattern, usually obtained from {@link
- * LdmlPatternInfo#parse(String)}, but any implementation of {@link AffixPatternProvider} is
- * accepted.
- */
- public void setPatternInfo(AffixPatternProvider patternInfo) {
- this.patternInfo = patternInfo;
- }
-
- /**
- * Sets attributes that imply changes to the literal interpretation of the pattern string affixes.
- *
- * @param signDisplay Whether to force a plus sign on positive numbers.
- * @param perMille Whether to substitute the percent sign in the pattern with a permille sign.
- */
- public void setPatternAttributes(SignDisplay signDisplay, boolean perMille) {
- this.signDisplay = signDisplay;
- this.perMilleReplacesPercent = perMille;
- }
-
- /**
- * Sets locale-specific details that affect the symbols substituted into the pattern string
- * affixes.
- *
- * @param symbols The desired instance of DecimalFormatSymbols.
- * @param currency The currency to be used when substituting currency values into the affixes.
- * Cannot be null, but a bogus currency like "XXX" can be used.
- * @param unitWidth The width used to render currencies.
- * @param rules Required if the triple currency sign, "¤¤¤", appears in the pattern, which can be
- * determined from the convenience method {@link #needsPlurals()}.
- */
- 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);
- }
- }
- }
-
- /**
- * Sets attributes of the current number being processed.
- *
- * @param isNegative Whether the number is negative.
- * @param plural The plural form of the number, required only if the pattern contains the triple
- * currency sign, "¤¤¤" (and as indicated by {@link #needsPlurals()}).
- */
- 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);
- }
-
- /**
- * 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 number properties in the current instance
- * are mutated; all other properties are left untouched.
- *
- * <p>The resulting modifier cannot be used in a QuantityChain.
- *
- * @return An immutable that supports both positive and negative numbers.
- */
- public ImmutableMurkyModifier createImmutable() {
- return createImmutableAndChain(null);
- }
-
- /**
- * 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 number properties in the current instance
- * are mutated; all other properties are left untouched.
- *
- * @param parent The QuantityChain to which to chain this immutable.
- * @return An immutable that supports both positive and negative numbers.
- */
- public ImmutableMurkyModifier createImmutableAndChain(QuantityChain parent) {
- 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, parent);
- } 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, parent);
- }
- }
-
- 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, QuantityChain parent) {
- this.positive = positive;
- this.negative = negative;
- this.parent = parent;
- }
-
- @Override
- public MicroProps withQuantity(FormatQuantity quantity) {
- assert parent != null;
- 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, QuantityChain parent) {
- assert mods.length == getModsLength();
- assert rules != null;
- this.mods = mods;
- this.rules = rules;
- this.parent = parent;
- }
-
- 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 MicroProps withQuantity(FormatQuantity quantity) {
- assert parent != null;
- 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;
- }
- }
-
- public QuantityChain addToChain(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;
- }
-
- @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
- && 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;
- } 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();
- }
-}
+++ /dev/null
-// © 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.Rounding;
-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) {
- if (minExponentDigits >= 0 && minExponentDigits < Rounding.MAX_VALUE) {
- NotationScientificImpl other = (NotationScientificImpl) this.clone();
- other.minExponentDigits = minExponentDigits;
- return other;
- } else {
- throw new IllegalArgumentException(
- "Integer digits must be between 0 and " + Rounding.MAX_VALUE);
- }
- }
-
- @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;
- }
- }
-}
+++ /dev/null
-// © 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.AtomicLongFieldUpdater;
-
-import com.ibm.icu.impl.number.FormatQuantity;
-import com.ibm.icu.impl.number.FormatQuantity4;
-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();
-
- 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_THRESHOLD = 12;
- static final int KEY_MAX = 13;
-
- 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);
- }
-
- static final AtomicLongFieldUpdater<NumberFormatterImpl> callCount =
- AtomicLongFieldUpdater.newUpdater(NumberFormatterImpl.class, "callCountInternal");
-
- // TODO: Reduce the number of fields.
- final NumberFormatterImpl parent;
- final int key;
- final Object value;
- volatile MacroProps resolvedMacros;
- volatile long callCountInternal; // do not access directly; use callCount instead
- volatile NumberFormatterImpl savedWithUnit;
- volatile Worker1 compiled;
-
- /** Base constructor; called during startup only. Sets the threshold to the default value of 3. */
- private NumberFormatterImpl() {
- parent = null;
- key = KEY_THRESHOLD;
- value = new Long(3);
- }
-
- /** 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);
- }
-
- /**
- * Internal fluent setter to support 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 NumberFormatterImpl threshold(Long threshold) {
- return new NumberFormatterImpl(this, KEY_THRESHOLD, threshold);
- }
-
- @Override
- public String toSkeleton() {
- return SkeletonBuilder.macrosToSkeleton(resolve());
- }
-
- @Override
- public NumberFormatterResult format(long input) {
- return format(new FormatQuantity4(input));
- }
-
- @Override
- public NumberFormatterResult format(double input) {
- return format(new FormatQuantity4(input));
- }
-
- @Override
- public NumberFormatterResult format(Number input) {
- return format(new FormatQuantity4(input));
- }
-
- @Override
- public NumberFormatterResult format(Measure input) {
- MeasureUnit unit = input.getUnit();
- Number number = input.getNumber();
- // Use this formatter if possible
- if (Objects.equals(resolve().unit, unit)) {
- return format(number);
- }
- // 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.format(number);
- }
-
- /**
- * This is the core entrypoint to the number formatting pipeline. It performs self-regulation: a
- * static code path for the first few calls, and compiling a more efficient data structure if
- * called repeatedly.
- *
- * @param fq The quantity to be formatted.
- * @return The formatted number result.
- */
- public NumberFormatterResult format(FormatQuantity fq) {
- MacroProps macros = resolve();
- NumberStringBuilder string = new NumberStringBuilder();
- long currentCount = callCount.incrementAndGet(this);
- MicroProps micros;
- if (currentCount == macros.threshold.longValue()) {
- compiled = Worker1.fromMacros(macros);
- micros = compiled.apply(fq, string);
- } else if (compiled != null) {
- micros = compiled.apply(fq, string);
- } else {
- micros = Worker1.applyStatic(macros, 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 != null) {
- 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;
- case KEY_THRESHOLD:
- if (macros.threshold == null) {
- macros.threshold = (Long) current.value;
- }
- break;
- default:
- throw new AssertionError("Unknown key: " + current.key);
- }
- current = current.parent;
- }
- resolvedMacros = macros;
- return macros;
- }
-}
+++ /dev/null
-// © 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 {
-
- /** Convenience method to create a NumberFormatterImpl directly. */
- public static NumberFormatterImpl create(
- Properties properties, DecimalFormatSymbols symbols, ULocale uloc) {
- MacroProps macros = oldToNew(properties, symbols, null);
- return NumberFormatterImpl.fromMacros(macros).locale(uloc);
- }
-
- /**
- * 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 != null;
- 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 != -1 || maxFrac != -1;
- boolean explicitMinMaxSig = minSig != -1 || maxSig != -1;
- // 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((byte) grouping1, (byte) grouping2, minGrouping == 2);
-
- /////////////
- // PADDING //
- /////////////
-
- if (properties.getFormatWidth() != -1) {
- macros.padding =
- PaddingImpl.getInstance(
- properties.getPadString(), properties.getFormatWidth(), properties.getPadPosition());
- }
-
- ///////////////////////////////
- // DECIMAL MARK ALWAYS SHOWN //
- ///////////////////////////////
-
- macros.decimal =
- properties.getDecimalSeparatorAlwaysShown()
- ? DecimalMarkDisplay.ALWAYS
- : DecimalMarkDisplay.AUTO;
-
- ///////////////////////
- // SIGN ALWAYS SHOWN //
- ///////////////////////
-
- macros.sign = properties.getSignAlwaysShown() ? SignDisplay.ALWAYS : SignDisplay.AUTO;
-
- /////////////////////////
- // SCIENTIFIC NOTATION //
- /////////////////////////
-
- if (properties.getMinimumExponentDigits() != -1) {
- // 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;
- // Bug #13289: if maxInt > minInt > 1, then minInt should be 1.
- // Clear out IntegerWidth to prevent padding extra zeros.
- if (maxInt > minInt && minInt > 1) {
- macros.integerWidth = null;
- }
- 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 : 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() != null) {
- 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() != 0) {
- macros.multiplier = new MultiplierImpl(properties.getMagnitudeMultiplier());
- } else if (properties.getMultiplier() != null) {
- 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);
- }
- }
-}
--- /dev/null
+// © 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.ThingsNeedingNewHome.PadPosition;
+
+public class Padder {
+
+ private static final Padder NONE = new Padder(null, -1, null);
+
+ String paddingString;
+ int targetWidth;
+ PadPosition position;
+
+ public Padder(String paddingString, int targetWidth, PadPosition position) {
+ // TODO: Add a few default instances
+ this.paddingString = (paddingString == null) ? " " : paddingString;
+ this.targetWidth = targetWidth;
+ this.position = (position == null) ? PadPosition.BEFORE_PREFIX : position;
+ }
+
+ public static Padder none() {
+ return NONE;
+ }
+
+ public static Padder codePoints(int cp, int targetWidth, PadPosition position) {
+ // TODO: Validate the code point
+ if (targetWidth >= 0) {
+ String paddingString = String.valueOf(Character.toChars(cp));
+ return new Padder(paddingString, targetWidth, position);
+ } else {
+ throw new IllegalArgumentException("Padding width must not be negative");
+ }
+ }
+
+ 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;
+ }
+}
\ No newline at end of file
+++ /dev/null
-// © 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.ThingsNeedingNewHome.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;
- }
-}
+++ /dev/null
-// © 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.FormatQuantity;
-import com.ibm.icu.impl.number.NumberStringBuilder;
-import com.ibm.icu.text.NumberFormat;
-
-import newapi.NumberFormatter.DecimalMarkDisplay;
-
-public class PositiveDecimalImpl {
-
- 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) {
- 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;
- }
-}
+++ /dev/null
-// © 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, QuantityChain parent) {
- this.data = data;
- this.rules = rules;
- this.parent = parent;
- }
-
- @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;
- }
-}
+++ /dev/null
-// © 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.");
- }
- }
-}
+++ /dev/null
-// © 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,
- QuantityChain parent) {
- return new ScientificImpl(notation, symbols, build, parent);
- }
-
- private ScientificImpl(
- NotationImpl.NotationScientificImpl notation,
- DecimalFormatSymbols symbols,
- boolean build,
- QuantityChain parent) {
- this.notation = notation;
- this.symbols = symbols;
- this.parent = parent;
-
- 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 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) {
- i += output.insert(i, symbols.getMinusSignString(), NumberFormat.Field.EXPONENT_SIGN);
- } else if (notation.exponentSignDisplay == SignDisplay.ALWAYS) {
- 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;
- }
-}
+++ /dev/null
-// © 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.ThingsNeedingNewHome.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.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) {
- sb.append('+');
- } else if (notation.exponentSignDisplay == SignDisplay.NEVER) {
- 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;
- c = safeCharAt(skeleton, offset++);
- }
- if (c == '!') {
- sign = SignDisplay.NEVER;
- 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.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((byte) (c0 - '0'), (byte) (c2 - '0'), c3 == '&');
- } else {
- result = GroupingImpl.getInstance((byte) (c0 - '0'), (byte) (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.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(padding.targetWidth);
- sb.append(':');
- sb.append(padding.position.name());
- sb.append(':');
- if (!padding.paddingString.equals(" ")) {
- sb.append(padding.paddingString);
- }
- }
-
- private static int skeletonToPadding(String skeleton, int offset, MacroProps output) {
- int originalOffset = offset;
- char c0 = skeleton.charAt(offset++);
- if (c0 == 'N') {
- offset += consumeUntil(skeleton, --offset, ' ', null);
- } else if ('0' <= c0 && c0 <= '9') {
- long intResult = consumeInt(skeleton, --offset);
- offset += intResult & 0xffffffff;
- int width = (int) (intResult >>> 32);
- char c1 = safeCharAt(skeleton, offset++);
- if (c1 != ':') {
- return offset - originalOffset - 1;
- }
- StringBuilder sb = new StringBuilder();
- offset += consumeUntil(skeleton, offset, ':', sb);
- String padPositionString = sb.toString();
- sb.setLength(0);
- offset += consumeUntil(skeleton, offset, ' ', sb);
- String string = (sb.length() == 0) ? " " : sb.toString();
- PadPosition position = Enum.valueOf(PadPosition.class, padPositionString);
- output.padding = PaddingImpl.getInstance(string, width, position);
- }
- return offset - originalOffset;
- }
-
- 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 int skeletonToIntegerWidth(String skeleton, int offset, MacroProps output) {
- int originalOffset = offset;
- long intResult = consumeInt(skeleton, offset);
- offset += intResult & 0xffffffff;
- int minInt = (int) (intResult >>> 32);
- char c1 = safeCharAt(skeleton, --offset);
- int maxInt;
- if (c1 == '-') {
- intResult = consumeInt(skeleton, offset);
- offset += intResult & 0xffffffff;
- maxInt = (int) (intResult >>> 32);
- }
- }
-
- 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 int skeletonToUnitWidth(String skeleton, int offset, MacroProps output) {
- int originalOffset = offset;
- StringBuilder sb = new StringBuilder();
- offset += consumeUntil(skeleton, offset, ' ', sb);
- output.unitWidth = Enum.valueOf(FormatWidth.class, sb.toString());
- return offset - originalOffset;
- }
-
- private static void signToSkeleton(SignDisplay value, StringBuilder sb) {
- sb.append(value.name());
- }
-
- private static int skeletonToSign(String skeleton, int offset, MacroProps output) {
- int originalOffset = offset;
- StringBuilder sb = new StringBuilder();
- offset += consumeUntil(skeleton, offset, ' ', sb);
- output.sign = Enum.valueOf(SignDisplay.class, sb.toString());
- return offset - originalOffset;
- }
-
- private static void decimalToSkeleton(DecimalMarkDisplay value, StringBuilder sb) {
- sb.append(value.name());
- }
-
- private static int skeletonToDecimal(String skeleton, int offset, MacroProps output) {
- int originalOffset = offset;
- StringBuilder sb = new StringBuilder();
- offset += consumeUntil(skeleton, offset, ' ', sb);
- output.decimal = Enum.valueOf(DecimalMarkDisplay.class, sb.toString());
- return offset - originalOffset;
- }
-
- 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) {
- if (sb != null) sb.append(c);
- c = safeCharAt(skeleton, offset++);
- }
- return offset - originalOffset;
- }
-
- private static long consumeInt(String skeleton, int offset) {
- int originalOffset = offset;
- char c = safeCharAt(skeleton, offset++);
- int result = 0;
- while ('0' <= c && c <= '9') {
- result = (result * 10) + (c - '0');
- c = safeCharAt(skeleton, offset++);
- }
- return (offset - originalOffset) | (((long) result) << 32);
- }
-}
+++ /dev/null
-// © 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(build);
- 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) {
- chain = input.multiplier.copyAndChain(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.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);
- } else {
- // No inner modifier required
- micros.modInner = ConstantAffixModifier.EMPTY;
- }
-
- // Middle modifier (patterns, positive/negative, currency symbols, percent)
- // The default middle modifier is weak (thus the false argument).
- 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.createImmutableAndChain(chain);
- } else {
- chain = murkyMod.addToChain(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);
- } 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;
- if (compactStyle == null) {
- // Use compact custom data
- chain =
- CompactImpl.getInstance(
- ((NotationImpl.NotationCompactImpl) input.notation).compactCustomData,
- rules,
- build ? murkyMod : null,
- chain);
- } else {
- CompactType compactType =
- (input.unit instanceof Currency) ? CompactType.CURRENCY : CompactType.DECIMAL;
- chain =
- CompactImpl.getInstance(
- input.loc, compactType, compactStyle, rules, build ? murkyMod : null, chain);
- }
- }
-
- 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;
- }
-}
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
-package newapi;
+package newapi.impl;
import java.math.RoundingMode;
import com.ibm.icu.util.MeasureUnit;
import com.ibm.icu.util.ULocale;
+import newapi.Grouper;
+import newapi.Notation;
+import newapi.NumberFormatter;
+import newapi.Rounder;
+import newapi.UnlocalizedNumberFormatter;
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))
- .notation(Notation.ENGINEERING.withMinExponentDigits(2))
- .notation(Notation.SIMPLE)
+ .notation(Notation.compactShort())
+ .notation(Notation.scientific().withExponentSignDisplay(SignDisplay.ALWAYS))
+ .notation(Notation.engineering().withMinExponentDigits(2))
+ .notation(Notation.simple())
.unit(Currency.getInstance("GBP"))
.unit(Dimensionless.PERCENT)
.unit(MeasureUnit.CUBIC_METER)
// (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))
+ .rounding(Rounder.fixedFraction(2).withMode(RoundingMode.HALF_UP))
+ .rounding(Rounder.integer().withMode(RoundingMode.CEILING))
+ .rounding(Rounder.currency(CurrencyUsage.STANDARD))
// .grouping(
// (int position, BigDecimal number) -> {
// return (position % 3) == 0;
// })
- .grouping(Grouping.DEFAULT)
- .grouping(Grouping.NONE)
- .grouping(Grouping.MIN_2_DIGITS)
+ .grouping(Grouper.defaults())
+ .grouping(Grouper.none())
+ .grouping(Grouper.min2())
// .padding(Padding.codePoints(' ', 8, PadPosition.AFTER_PREFIX))
.sign(SignDisplay.ALWAYS)
.decimal(DecimalMarkDisplay.ALWAYS)
import com.ibm.icu.util.CurrencyAmount;
import com.ibm.icu.util.ULocale;
-import newapi.NumberFormatter.LocalizedNumberFormatter;
-import newapi.impl.MacroProps;
-import newapi.impl.NumberFormatterImpl;
-import newapi.impl.NumberPropertyMapper;
+import newapi.LocalizedNumberFormatter;
+import newapi.NumberPropertyMapper;
public class NumberFormatDataDrivenTest {
: PatternString.IGNORE_ROUNDING_NEVER);
propertiesFromTuple(tuple, properties);
DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(locale);
- MacroProps macros = NumberPropertyMapper.oldToNew(properties, symbols, null);
- LocalizedNumberFormatter fmt = NumberFormatterImpl.fromMacros(macros).locale(locale);
+ LocalizedNumberFormatter fmt = NumberPropertyMapper.create(properties, symbols).locale(locale);
Number number = toNumber(tuple.format);
String expected = tuple.output;
String actual = fmt.format(number).toString();
import com.ibm.icu.impl.number.FormatQuantity4;
import com.ibm.icu.impl.number.Properties;
import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
+import com.ibm.icu.text.DecimalFormatSymbols;
import com.ibm.icu.util.ULocale;
-import newapi.impl.NumberFormatterImpl;
-import newapi.impl.NumberPropertyMapper;
+import newapi.LocalizedNumberFormatter;
+import newapi.NumberPropertyMapper;
/** TODO: This is a temporary name for this class. Suggestions for a better name? */
public class FormatQuantityTest extends TestFmwk {
public void testBehavior() throws ParseException {
// Make a list of several formatters to test the behavior of FormatQuantity.
- List<NumberFormatterImpl> formats = new ArrayList<NumberFormatterImpl>();
+ List<LocalizedNumberFormatter> formats = new ArrayList<LocalizedNumberFormatter>();
+
+ DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(ULocale.ENGLISH);
Properties properties = new Properties();
- formats.add(NumberPropertyMapper.create(properties, null, ULocale.ENGLISH));
+ formats.add(NumberPropertyMapper.create(properties, symbols).locale(ULocale.ENGLISH));
properties =
new Properties()
.setMinimumSignificantDigits(3)
.setMaximumSignificantDigits(3)
.setCompactStyle(CompactStyle.LONG);
- formats.add(NumberPropertyMapper.create(properties, null, ULocale.ENGLISH));
+ formats.add(NumberPropertyMapper.create(properties, symbols).locale(ULocale.ENGLISH));
properties =
new Properties()
.setMinimumExponentDigits(1)
.setMaximumIntegerDigits(3)
.setMaximumFractionDigits(1);
- formats.add(NumberPropertyMapper.create(properties, null, ULocale.ENGLISH));
+ formats.add(NumberPropertyMapper.create(properties, symbols).locale(ULocale.ENGLISH));
properties = new Properties().setRoundingIncrement(new BigDecimal("0.5"));
- formats.add(NumberPropertyMapper.create(properties, null, ULocale.ENGLISH));
+ formats.add(NumberPropertyMapper.create(properties, symbols).locale(ULocale.ENGLISH));
String[] cases = {
"1.0",
}
}
- static void testFormatQuantity(int t, String str, List<NumberFormatterImpl> formats, int mode) {
+ static void testFormatQuantity(int t, String str, List<LocalizedNumberFormatter> formats, int mode) {
if (mode == 2) {
assertEquals("Double is not valid", Double.toString(Double.parseDouble(str)), str);
}
}
private static void testFormatQuantityWithFormats(
- FormatQuantity rq0, FormatQuantity rq1, List<NumberFormatterImpl> formats) {
- for (NumberFormatterImpl format : formats) {
+ FormatQuantity rq0, FormatQuantity rq1, List<LocalizedNumberFormatter> formats) {
+ for (LocalizedNumberFormatter format : formats) {
FormatQuantity q0 = rq0.createCopy();
FormatQuantity q1 = rq1.createCopy();
String s1 = format.format(q0).toString();
import com.ibm.icu.util.Currency;
import com.ibm.icu.util.ULocale;
+import newapi.MurkyModifier;
import newapi.NumberFormatter.SignDisplay;
-import newapi.impl.MurkyModifier;
public class MurkyModifierTest {
import static org.junit.Assert.assertNotEquals;
import java.math.BigDecimal;
-import java.math.RoundingMode;
import java.util.Locale;
import org.junit.Ignore;
import com.ibm.icu.util.MeasureUnit;
import com.ibm.icu.util.ULocale;
+import newapi.FormattedNumber;
+import newapi.Grouper;
+import newapi.IntegerWidth;
+import newapi.LocalizedNumberFormatter;
+import newapi.Notation;
import newapi.NumberFormatter;
import newapi.NumberFormatter.DecimalMarkDisplay;
-import newapi.NumberFormatter.Grouping;
-import newapi.NumberFormatter.IRounding;
-import newapi.NumberFormatter.IntegerWidth;
-import newapi.NumberFormatter.LocalizedNumberFormatter;
-import newapi.NumberFormatter.Notation;
-import newapi.NumberFormatter.NumberFormatterResult;
-import newapi.NumberFormatter.Padding;
-import newapi.NumberFormatter.Rounding;
import newapi.NumberFormatter.SignDisplay;
-import newapi.NumberFormatter.UnlocalizedNumberFormatter;
-import newapi.impl.NumberFormatterImpl;
+import newapi.impl.Padder;
+import newapi.NumberPropertyMapper;
+import newapi.Rounder;
+import newapi.UnlocalizedNumberFormatter;
public class NumberFormatterTest {
assertFormatDescending(
"Scientific",
"E",
- NumberFormatter.with().notation(Notation.SCIENTIFIC),
+ NumberFormatter.with().notation(Notation.scientific()),
ULocale.ENGLISH,
"8.765E4",
"8.765E3",
assertFormatDescending(
"Engineering",
"E3",
- NumberFormatter.with().notation(Notation.ENGINEERING),
+ NumberFormatter.with().notation(Notation.engineering()),
ULocale.ENGLISH,
"87.65E3",
"8.765E3",
"Scientific sign always shown",
"E+",
NumberFormatter.with()
- .notation(Notation.SCIENTIFIC.withExponentSignDisplay(SignDisplay.ALWAYS)),
+ .notation(Notation.scientific().withExponentSignDisplay(SignDisplay.ALWAYS)),
ULocale.ENGLISH,
"8.765E+4",
"8.765E+3",
assertFormatDescending(
"Scientific min exponent digits",
"E00",
- NumberFormatter.with().notation(Notation.SCIENTIFIC.withMinExponentDigits(2)),
+ NumberFormatter.with().notation(Notation.scientific().withMinExponentDigits(2)),
ULocale.ENGLISH,
"8.765E04",
"8.765E03",
assertFormatSingle(
"Scientific Negative",
"E",
- NumberFormatter.with().notation(Notation.SCIENTIFIC),
+ NumberFormatter.with().notation(Notation.scientific()),
ULocale.ENGLISH,
-1000000,
"-1E6");
assertFormatDescending(
"Compact Short",
"C",
- NumberFormatter.with().notation(Notation.COMPACT_SHORT),
+ NumberFormatter.with().notation(Notation.compactShort()),
ULocale.ENGLISH,
"88K",
"8.8K",
assertFormatDescending(
"Compact Long",
"CC",
- NumberFormatter.with().notation(Notation.COMPACT_LONG),
+ NumberFormatter.with().notation(Notation.compactLong()),
ULocale.ENGLISH,
"88 thousand",
"8.8 thousand",
assertFormatDescending(
"Compact Short Currency",
"C $USD",
- NumberFormatter.with().notation(Notation.COMPACT_SHORT).unit(USD),
+ NumberFormatter.with().notation(Notation.compactShort()).unit(USD),
ULocale.ENGLISH,
"$88K",
"$8.8K",
assertFormatDescending(
"Compact Long Currency",
"CC $USD",
- NumberFormatter.with().notation(Notation.COMPACT_LONG).unit(USD),
+ NumberFormatter.with().notation(Notation.compactLong()).unit(USD),
ULocale.ENGLISH,
"$88K",
"$8.8K",
assertFormatSingle(
"Compact Plural One",
"CC",
- NumberFormatter.with().notation(Notation.COMPACT_LONG),
+ NumberFormatter.with().notation(Notation.compactLong()),
ULocale.forLanguageTag("es"),
1000000,
"1 millón");
assertFormatSingle(
"Compact Plural Other",
"CC",
- NumberFormatter.with().notation(Notation.COMPACT_LONG),
+ NumberFormatter.with().notation(Notation.compactLong()),
ULocale.forLanguageTag("es"),
2000000,
"2 millones");
assertFormatSingle(
"Compact with Negative Sign",
"C",
- NumberFormatter.with().notation(Notation.COMPACT_SHORT),
+ NumberFormatter.with().notation(Notation.compactShort()),
ULocale.ENGLISH,
-9876543.21,
"-9.9M");
"Compact Meters Long",
"CC U:length:meter unit-width=WIDE",
NumberFormatter.with()
- .notation(Notation.COMPACT_LONG)
+ .notation(Notation.compactLong())
.unit(MeasureUnit.METER)
.unitWidth(FormatWidth.WIDE),
ULocale.ENGLISH,
assertFormatSingle(
"Currency Long Name from Pattern Syntax",
- "$GBP F0 grouping=NONE integer-width=1- symbols=loc:en_US sign=AUTO decimal=AUTO",
- NumberFormatterImpl.fromPattern("0 ¤¤¤", DecimalFormatSymbols.getInstance()).unit(GBP),
+ "$GBP F0 grouping=none integer-width=1- symbols=loc:en_US sign=AUTO decimal=AUTO",
+ NumberPropertyMapper.create("0 ¤¤¤", DecimalFormatSymbols.getInstance()).unit(GBP),
ULocale.ENGLISH,
1234567.89,
"1234568 British pounds");
assertFormatDescending(
"Integer",
"F0",
- NumberFormatter.with().rounding(Rounding.INTEGER),
+ NumberFormatter.with().rounding(Rounder.integer()),
ULocale.ENGLISH,
"87,650",
"8,765",
assertFormatDescending(
"Fixed Fraction",
"F3",
- NumberFormatter.with().rounding(Rounding.fixedFraction(3)),
+ NumberFormatter.with().rounding(Rounder.fixedFraction(3)),
ULocale.ENGLISH,
"87,650.000",
"8,765.000",
assertFormatDescending(
"Min Fraction",
"F1-",
- NumberFormatter.with().rounding(Rounding.minFraction(1)),
+ NumberFormatter.with().rounding(Rounder.minFraction(1)),
ULocale.ENGLISH,
"87,650.0",
"8,765.0",
assertFormatDescending(
"Max Fraction",
"F-1",
- NumberFormatter.with().rounding(Rounding.maxFraction(1)),
+ NumberFormatter.with().rounding(Rounder.maxFraction(1)),
ULocale.ENGLISH,
"87,650",
"8,765",
assertFormatDescending(
"Min/Max Fraction",
"F1-3",
- NumberFormatter.with().rounding(Rounding.minMaxFraction(1, 3)),
+ NumberFormatter.with().rounding(Rounder.minMaxFraction(1, 3)),
ULocale.ENGLISH,
"87,650.0",
"8,765.0",
assertFormatSingle(
"Fixed Significant",
"S3",
- NumberFormatter.with().rounding(Rounding.fixedFigures(3)),
+ NumberFormatter.with().rounding(Rounder.fixedFigures(3)),
ULocale.ENGLISH,
-98,
"-98.0");
assertFormatSingle(
"Fixed Significant Rounding",
"S3",
- NumberFormatter.with().rounding(Rounding.fixedFigures(3)),
+ NumberFormatter.with().rounding(Rounder.fixedFigures(3)),
ULocale.ENGLISH,
-98.7654321,
"-98.8");
assertFormatSingle(
"Fixed Significant Zero",
"S3",
- NumberFormatter.with().rounding(Rounding.fixedFigures(3)),
+ NumberFormatter.with().rounding(Rounder.fixedFigures(3)),
ULocale.ENGLISH,
0,
"0.00");
assertFormatSingle(
"Min Significant",
"S2-",
- NumberFormatter.with().rounding(Rounding.minFigures(2)),
+ NumberFormatter.with().rounding(Rounder.minFigures(2)),
ULocale.ENGLISH,
-9,
"-9.0");
assertFormatSingle(
"Max Significant",
"S-4",
- NumberFormatter.with().rounding(Rounding.maxFigures(4)),
+ NumberFormatter.with().rounding(Rounder.maxFigures(4)),
ULocale.ENGLISH,
98.7654321,
"98.77");
assertFormatSingle(
"Min/Max Significant",
"S3-4",
- NumberFormatter.with().rounding(Rounding.minMaxFigures(3, 4)),
+ NumberFormatter.with().rounding(Rounder.minMaxFigures(3, 4)),
ULocale.ENGLISH,
9.99999,
"10.0");
assertFormatDescending(
"Basic Significant", // for comparison
"S-2",
- NumberFormatter.with().rounding(Rounding.maxFigures(2)),
+ NumberFormatter.with().rounding(Rounder.maxFigures(2)),
ULocale.ENGLISH,
"88,000",
"8,800",
assertFormatDescending(
"FracSig minMaxFrac minSig",
"F1-2>3",
- NumberFormatter.with().rounding(Rounding.minMaxFraction(1, 2).withMinFigures(3)),
+ NumberFormatter.with().rounding(Rounder.minMaxFraction(1, 2).withMinFigures(3)),
ULocale.ENGLISH,
"87,650.0",
"8,765.0",
assertFormatDescending(
"FracSig minMaxFrac maxSig A",
"F1-3<2",
- NumberFormatter.with().rounding(Rounding.minMaxFraction(1, 3).withMaxFigures(2)),
+ NumberFormatter.with().rounding(Rounder.minMaxFraction(1, 3).withMaxFigures(2)),
ULocale.ENGLISH,
"88,000.0", // maxSig beats maxFrac
"8,800.0", // maxSig beats maxFrac
assertFormatDescending(
"FracSig minMaxFrac maxSig B",
"F2<2",
- NumberFormatter.with().rounding(Rounding.fixedFraction(2).withMaxFigures(2)),
+ NumberFormatter.with().rounding(Rounder.fixedFraction(2).withMaxFigures(2)),
ULocale.ENGLISH,
"88,000.00", // maxSig beats maxFrac
"8,800.00", // maxSig beats maxFrac
assertFormatDescending(
"Rounding None",
"Y",
- NumberFormatter.with().rounding(Rounding.NONE),
+ NumberFormatter.with().rounding(Rounder.none()),
ULocale.ENGLISH,
"87,650",
"8,765",
assertFormatDescending(
"Increment",
"M0.5",
- NumberFormatter.with().rounding(Rounding.increment(BigDecimal.valueOf(0.5))),
+ NumberFormatter.with().rounding(Rounder.increment(BigDecimal.valueOf(0.5))),
ULocale.ENGLISH,
"87,650.0",
"8,765.0",
assertFormatDescending(
"Currency Standard",
"$CZK GSTANDARD",
- NumberFormatter.with().rounding(Rounding.currency(CurrencyUsage.STANDARD)).unit(CZK),
+ NumberFormatter.with().rounding(Rounder.currency(CurrencyUsage.STANDARD)).unit(CZK),
ULocale.ENGLISH,
"CZK 87,650.00",
"CZK 8,765.00",
assertFormatDescending(
"Currency Cash",
"$CZK GCASH",
- NumberFormatter.with().rounding(Rounding.currency(CurrencyUsage.CASH)).unit(CZK),
+ NumberFormatter.with().rounding(Rounder.currency(CurrencyUsage.CASH)).unit(CZK),
ULocale.ENGLISH,
"CZK 87,650",
"CZK 8,765",
assertFormatDescending(
"Currency not in top-level fluent chain",
"F0",
- NumberFormatter.with().rounding(Rounding.currency(CurrencyUsage.CASH).withCurrency(CZK)),
+ NumberFormatter.with().rounding(Rounder.currency(CurrencyUsage.CASH).withCurrency(CZK)),
ULocale.ENGLISH,
"87,650",
"8,765",
"0",
"0",
"0");
-
- assertFormatDescending(
- "Lambda Function",
- "",
- NumberFormatter.with()
- .rounding(
- new IRounding() {
- @Override
- public BigDecimal round(BigDecimal input) {
- return input.setScale(2, RoundingMode.FLOOR);
- }
- }),
- ULocale.ENGLISH,
- "87,650.00",
- "8,765.00",
- "876.50",
- "87.65",
- "8.76",
- "0.87",
- "0.08",
- "0.00",
- "0.00");
}
@Test
// Note that en-US is already performed in the unitPercent() function.
assertFormatDescending(
"Indic Grouping",
- "%% grouping=DEFAULT",
- NumberFormatter.with().unit(Dimensionless.PERMILLE).grouping(Grouping.DEFAULT),
+ "%% grouping=defaults",
+ NumberFormatter.with().unit(Dimensionless.PERMILLE).grouping(Grouper.defaults()),
new ULocale("en-IN"),
"8,76,50,000‰",
"87,65,000‰",
assertFormatDescending(
"Western Grouping, Min 2",
- "%% grouping=DEFAULT_MIN_2_DIGITS",
- NumberFormatter.with().unit(Dimensionless.PERMILLE).grouping(Grouping.MIN_2_DIGITS),
+ "%% grouping=min2",
+ NumberFormatter.with().unit(Dimensionless.PERMILLE).grouping(Grouper.min2()),
ULocale.ENGLISH,
"87,650,000‰",
"8,765,000‰",
assertFormatDescending(
"Indic Grouping, Min 2",
- "%% grouping=DEFAULT_MIN_2_DIGITS",
- NumberFormatter.with().unit(Dimensionless.PERMILLE).grouping(Grouping.MIN_2_DIGITS),
+ "%% grouping=min2",
+ NumberFormatter.with().unit(Dimensionless.PERMILLE).grouping(Grouper.min2()),
new ULocale("en-IN"),
"8,76,50,000‰",
"87,65,000‰",
assertFormatDescending(
"No Grouping",
- "%% grouping=NONE",
- NumberFormatter.with().unit(Dimensionless.PERMILLE).grouping(Grouping.NONE),
+ "%% grouping=none",
+ NumberFormatter.with().unit(Dimensionless.PERMILLE).grouping(Grouper.none()),
new ULocale("en-IN"),
"87650000‰",
"8765000‰",
public void padding() {
assertFormatDescending(
"Padding",
- "padding=NONE",
- NumberFormatter.with().padding(Padding.NONE),
+ "",
+ NumberFormatter.with().padding(Padder.none()),
ULocale.ENGLISH,
"87,650",
"8,765",
assertFormatDescending(
"Padding",
- "padding=8:AFTER_PREFIX:*",
- NumberFormatter.with().padding(Padding.codePoints('*', 8, PadPosition.AFTER_PREFIX)),
+ "",
+ NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.AFTER_PREFIX)),
ULocale.ENGLISH,
"**87,650",
"***8,765",
assertFormatDescending(
"Padding with code points",
- "padding=8:AFTER_PREFIX:𐇤",
- NumberFormatter.with().padding(Padding.codePoints(0x101E4, 8, PadPosition.AFTER_PREFIX)),
+ "",
+ NumberFormatter.with().padding(Padder.codePoints(0x101E4, 8, PadPosition.AFTER_PREFIX)),
ULocale.ENGLISH,
"𐇤𐇤87,650",
"𐇤𐇤𐇤8,765",
assertFormatDescending(
"Padding with wide digits",
- "padding=8:AFTER_PREFIX:* symbols=ns:mathsanb",
+ "symbols=ns:mathsanb",
NumberFormatter.with()
- .padding(Padding.codePoints('*', 8, PadPosition.AFTER_PREFIX))
+ .padding(Padder.codePoints('*', 8, PadPosition.AFTER_PREFIX))
.symbols(NumberingSystem.getInstanceByName("mathsanb")),
ULocale.ENGLISH,
"**𝟴𝟳,𝟲𝟱𝟬",
assertFormatDescending(
"Padding with currency spacing",
- "$GBP padding=10:AFTER_PREFIX:* unit-width=SHORT",
+ "$GBP unit-width=SHORT",
NumberFormatter.with()
- .padding(Padding.codePoints('*', 10, PadPosition.AFTER_PREFIX))
+ .padding(Padder.codePoints('*', 10, PadPosition.AFTER_PREFIX))
.unit(GBP)
.unitWidth(FormatWidth.SHORT),
ULocale.ENGLISH,
assertFormatSingle(
"Pad Before Prefix",
- "padding=8:BEFORE_PREFIX:*",
- NumberFormatter.with().padding(Padding.codePoints('*', 8, PadPosition.BEFORE_PREFIX)),
+ "",
+ NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.BEFORE_PREFIX)),
ULocale.ENGLISH,
-88.88,
"**-88.88");
assertFormatSingle(
"Pad After Prefix",
- "padding=8:AFTER_PREFIX:*",
- NumberFormatter.with().padding(Padding.codePoints('*', 8, PadPosition.AFTER_PREFIX)),
+ "",
+ NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.AFTER_PREFIX)),
ULocale.ENGLISH,
-88.88,
"-**88.88");
assertFormatSingle(
"Pad Before Suffix",
- "% padding=8:BEFORE_SUFFIX:*",
+ "%",
NumberFormatter.with()
- .padding(Padding.codePoints('*', 8, PadPosition.BEFORE_SUFFIX))
+ .padding(Padder.codePoints('*', 8, PadPosition.BEFORE_SUFFIX))
.unit(Dimensionless.PERCENT),
ULocale.ENGLISH,
0.8888,
assertFormatSingle(
"Pad After Suffix",
- "% padding=8:AFTER_SUFFIX:*",
+ "%",
NumberFormatter.with()
- .padding(Padding.codePoints('*', 8, PadPosition.AFTER_SUFFIX))
+ .padding(Padder.codePoints('*', 8, PadPosition.AFTER_SUFFIX))
.unit(Dimensionless.PERCENT),
ULocale.ENGLISH,
0.8888,
assertFormatDescending(
"Integer Width Default",
"integer-width=1-",
- NumberFormatter.with().integerWidth(IntegerWidth.DEFAULT),
+ NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(1)),
ULocale.ENGLISH,
"87,650",
"8,765",
assertFormatDescending(
"Integer Width Max 3",
"integer-width=1-3",
- NumberFormatter.with().integerWidth(IntegerWidth.DEFAULT.truncateAt(3)),
+ NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(1).truncateAt(3)),
ULocale.ENGLISH,
"650",
"765",
"French Symbols with Japanese Data 2",
"C symbols=loc:fr",
NumberFormatter.with()
- .notation(Notation.COMPACT_SHORT)
+ .notation(Notation.compactShort())
.symbols(DecimalFormatSymbols.getInstance(ULocale.FRENCH)),
ULocale.JAPAN,
12345,
String posSuffix = (String) cas[2];
String negPrefix = (String) cas[3];
String negSuffix = (String) cas[4];
- NumberFormatterResult positive = f.format(1);
- NumberFormatterResult negative = f.format(-1);
+ FormattedNumber positive = f.format(1);
+ FormattedNumber negative = f.format(-1);
assertEquals(posPrefix, positive.getPrefix());
assertEquals(posSuffix, positive.getSuffix());
assertEquals(negPrefix, negative.getPrefix());
NumberFormatter.with()
.unit(USD)
.unitWidth(FormatWidth.WIDE)
- .rounding(Rounding.fixedFraction(0)),
+ .rounding(Rounder.fixedFraction(0)),
ULocale.ENGLISH,
1,
"1 US dollar");
NumberFormatter.with()
.unit(USD)
.unitWidth(FormatWidth.WIDE)
- .rounding(Rounding.fixedFraction(2)),
+ .rounding(Rounder.fixedFraction(2)),
ULocale.ENGLISH,
1,
"1.00 US dollars");
assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton());
final double[] inputs =
new double[] {87650, 8765, 876.5, 87.65, 8.765, 0.8765, 0.08765, 0.008765, 0};
- NumberFormatterImpl l1 = ((NumberFormatterImpl) f).threshold(0L).locale(locale); // no self-regulation
- NumberFormatterImpl l2 = ((NumberFormatterImpl) f).threshold(1L).locale(locale); // all self-regulation
+ LocalizedNumberFormatter l1 = f.threshold(0L).locale(locale); // no self-regulation
+ LocalizedNumberFormatter l2 = f.threshold(1L).locale(locale); // all self-regulation
for (int i = 0; i < 9; i++) {
double d = inputs[i];
String actual1 = l1.format(d).toString();
Number input,
String expected) {
assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton());
- NumberFormatterImpl l1 = ((NumberFormatterImpl) f).threshold(0L).locale(locale); // no self-regulation
- NumberFormatterImpl l2 = ((NumberFormatterImpl) f).threshold(1L).locale(locale); // all self-regulation
+ LocalizedNumberFormatter l1 = f.threshold(0L).locale(locale); // no self-regulation
+ LocalizedNumberFormatter l2 = f.threshold(1L).locale(locale); // all self-regulation
String actual1 = l1.format(input).toString();
assertEquals(message + ": L1: " + input, expected, actual1);
String actual2 = l2.format(input).toString();
Measure input,
String expected) {
assertEquals(message + ": Skeleton:", skeleton, f.toSkeleton());
- NumberFormatterImpl l1 = ((NumberFormatterImpl) f).threshold(0L).locale(locale); // no self-regulation
- NumberFormatterImpl l2 = ((NumberFormatterImpl) f).threshold(1L).locale(locale); // all self-regulation
+ LocalizedNumberFormatter l1 = f.threshold(0L).locale(locale); // no self-regulation
+ LocalizedNumberFormatter l2 = f.threshold(1L).locale(locale); // all self-regulation
String actual1 = l1.format(input).toString();
assertEquals(message + ": L1: " + input, expected, actual1);
String actual2 = l2.format(input).toString();