]> granicus.if.org Git - icu/commitdiff
ICU-11276 Adding initial Java NumberRangeFormatter boilerplate.
authorShane Carr <shane@unicode.org>
Wed, 29 Aug 2018 02:50:55 +0000 (19:50 -0700)
committerShane Carr <shane@unicode.org>
Thu, 27 Sep 2018 21:27:39 +0000 (14:27 -0700)
icu4j/main/classes/core/src/com/ibm/icu/impl/number/range/RangeMacroProps.java [new file with mode: 0644]
icu4j/main/classes/core/src/com/ibm/icu/number/FormattedNumberRange.java [new file with mode: 0644]
icu4j/main/classes/core/src/com/ibm/icu/number/LocalizedNumberRangeFormatter.java [new file with mode: 0644]
icu4j/main/classes/core/src/com/ibm/icu/number/NumberRangeFormatter.java [new file with mode: 0644]
icu4j/main/classes/core/src/com/ibm/icu/number/NumberRangeFormatterSettings.java [new file with mode: 0644]
icu4j/main/classes/core/src/com/ibm/icu/number/UnlocalizedNumberRangeFormatter.java [new file with mode: 0644]

diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/range/RangeMacroProps.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/range/RangeMacroProps.java
new file mode 100644 (file)
index 0000000..9595e7d
--- /dev/null
@@ -0,0 +1,47 @@
+// © 2018 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package com.ibm.icu.impl.number.range;
+
+import java.util.Objects;
+
+import com.ibm.icu.number.NumberFormatterSettings;
+import com.ibm.icu.number.NumberRangeFormatter.RangeCollapse;
+import com.ibm.icu.number.NumberRangeFormatter.RangeIdentityFallback;
+import com.ibm.icu.util.ULocale;
+
+/**
+ * @author sffc
+ *
+ */
+public class RangeMacroProps {
+    public NumberFormatterSettings<?> formatter1;
+    public NumberFormatterSettings<?> formatter2;
+    public RangeCollapse collapse;
+    public RangeIdentityFallback identityFallback;
+    public ULocale loc;
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(formatter1,
+                formatter2,
+                collapse,
+                identityFallback,
+                loc);
+    }
+
+    @Override
+    public boolean equals(Object _other) {
+        if (_other == null)
+            return false;
+        if (this == _other)
+            return true;
+        if (!(_other instanceof RangeMacroProps))
+            return false;
+        RangeMacroProps other = (RangeMacroProps) _other;
+        return Objects.equals(formatter1, other.formatter1)
+                && Objects.equals(formatter2, other.formatter2)
+                && Objects.equals(collapse, other.collapse)
+                && Objects.equals(identityFallback, other.identityFallback)
+                && Objects.equals(loc, other.loc);
+    }
+}
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/FormattedNumberRange.java b/icu4j/main/classes/core/src/com/ibm/icu/number/FormattedNumberRange.java
new file mode 100644 (file)
index 0000000..f3add32
--- /dev/null
@@ -0,0 +1,211 @@
+// © 2018 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package com.ibm.icu.number;
+
+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.DecimalQuantity;
+import com.ibm.icu.impl.number.NumberStringBuilder;
+import com.ibm.icu.util.ICUUncheckedIOException;
+
+/**
+ * The result of a number range formatting operation. This class allows the result to be exported in several data types,
+ * including a String, an AttributedCharacterIterator, and a BigDecimal.
+ *
+ * @author sffc
+ * @draft ICU 62
+ * @provisional This API might change or be removed in a future release.
+ * @see NumberRangeFormatter
+ */
+public class FormattedNumberRange {
+    final NumberStringBuilder nsb;
+    final DecimalQuantity first;
+    final DecimalQuantity second;
+    final RangeIdentityType identityType;
+
+    public static enum RangeIdentityType {
+        EQUAL_BEFORE_ROUNDING, EQUAL_AFTER_ROUNDING, NOT_EQUAL
+    }
+
+    FormattedNumberRange(NumberStringBuilder nsb, DecimalQuantity first, DecimalQuantity second,
+            RangeIdentityType identityType) {
+        this.nsb = nsb;
+        this.first = first;
+        this.second = second;
+        this.identityType = identityType;
+    }
+
+    /**
+     * Creates a String representation of the the formatted number range.
+     *
+     * @return a String containing the localized number range.
+     * @draft ICU 63
+     * @provisional This API might change or be removed in a future release.
+     * @see NumberRangeFormatter
+     */
+    @Override
+    public String toString() {
+        return nsb.toString();
+    }
+
+    /**
+     * Append the formatted number range to an Appendable, such as a StringBuilder. This may be slightly more efficient
+     * than creating a String.
+     *
+     * <p>
+     * If an IOException occurs when appending to the Appendable, an unchecked {@link ICUUncheckedIOException} is thrown
+     * instead.
+     *
+     * @param appendable
+     *            The Appendable to which to append the formatted number range string.
+     * @return The same Appendable, for chaining.
+     * @draft ICU 63
+     * @provisional This API might change or be removed in a future release.
+     * @see Appendable
+     * @see NumberRangeFormatter
+     */
+    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;
+    }
+
+    /**
+     * Determines the start and end indices of the next occurrence of the given <em>field</em> in the output string.
+     * This allows you to determine the locations of, for example, the integer part, fraction part, or symbols.
+     * <p>
+     * If both sides of the range have the same field, the field will occur twice, once before the range separator and
+     * once after the range separator, if applicable.
+     * <p>
+     * If a field occurs just once, calling this method will find that occurrence and return it. If a field occurs
+     * multiple times, this method may be called repeatedly with the following pattern:
+     *
+     * <pre>
+     * FieldPosition fpos = new FieldPosition(NumberFormat.Field.GROUPING_SEPARATOR);
+     * while (formattedNumber.nextFieldPosition(fpos, status)) {
+     *     // do something with fpos.
+     * }
+     * </pre>
+     * <p>
+     * This method is useful if you know which field to query. If you want all available field position information, use
+     * {@link #toCharacterIterator()}.
+     *
+     * @param fieldPosition
+     *            Input+output variable. See {@link FormattedNumber#nextFieldPosition(FieldPosition)}.
+     * @return true if a new occurrence of the field was found; false otherwise.
+     * @draft ICU 63
+     * @provisional This API might change or be removed in a future release.
+     * @see com.ibm.icu.text.NumberFormat.Field
+     * @see NumberRangeFormatter
+     */
+    public boolean nextFieldPosition(FieldPosition fieldPosition) {
+        return nsb.nextFieldPosition(fieldPosition);
+    }
+
+    /**
+     * Export the formatted number range as an AttributedCharacterIterator. This allows you to determine which
+     * characters in the output string correspond to which <em>fields</em>, such as the integer part, fraction part, and
+     * sign.
+     * <p>
+     * If information on only one field is needed, use {@link #nextFieldPosition(FieldPosition)} instead.
+     *
+     * @return An AttributedCharacterIterator, containing information on the field attributes of the number range
+     *         string.
+     * @draft ICU 63
+     * @provisional This API might change or be removed in a future release.
+     * @see com.ibm.icu.text.NumberFormat.Field
+     * @see AttributedCharacterIterator
+     * @see NumberRangeFormatter
+     */
+    public AttributedCharacterIterator toCharacterIterator() {
+        return nsb.toCharacterIterator();
+    }
+
+    /**
+     * Export the first formatted number as a BigDecimal. This endpoint is useful for obtaining the exact number being
+     * printed after scaling and rounding have been applied by the number range formatting pipeline.
+     *
+     * @return A BigDecimal representation of the first formatted number.
+     * @draft ICU 63
+     * @provisional This API might change or be removed in a future release.
+     * @see NumberRangeFormatter
+     * @see #getSecondBigDecimal
+     */
+    public BigDecimal getFirstBigDecimal() {
+        return first.toBigDecimal();
+    }
+
+    /**
+     * Export the second formatted number as a BigDecimal. This endpoint is useful for obtaining the exact number being
+     * printed after scaling and rounding have been applied by the number range formatting pipeline.
+     *
+     * @return A BigDecimal representation of the second formatted number.
+     * @draft ICU 63
+     * @provisional This API might change or be removed in a future release.
+     * @see NumberRangeFormatter
+     * @see #getFirstBigDecimal
+     */
+    public BigDecimal getSecondBigDecimal() {
+        return second.toBigDecimal();
+    }
+
+    /**
+     * Returns whether the pair of numbers was successfully formatted as a range or whether an identity fallback was
+     * used. For example, if the first and second number were the same either before or after rounding occurred, an
+     * identity fallback was used.
+     *
+     * @return A IdentityType indicating the resulting identity situation in the formatted number range.
+     * @draft ICU 63
+     * @provisional This API might change or be removed in a future release.
+     * @see NumberRangeFormatter
+     * @see NumberRangeFormatter.RangeIdentityFallback
+     */
+    public RangeIdentityType getIdentityType() {
+        return identityType;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @draft ICU 63
+     * @provisional This API might change or be removed in a future release.
+     */
+    @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())
+                ^ first.toBigDecimal().hashCode() ^ second.toBigDecimal().hashCode();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @draft ICU 63
+     * @provisional This API might change or be removed in a future release.
+     */
+    @Override
+    public boolean equals(Object other) {
+        if (this == other)
+            return true;
+        if (other == null)
+            return false;
+        if (!(other instanceof FormattedNumberRange))
+            return false;
+        // NumberStringBuilder and BigDecimal are mutable, so we can't call
+        // #equals() or #hashCode() on them directly.
+        FormattedNumberRange _other = (FormattedNumberRange) other;
+        return Arrays.equals(nsb.toCharArray(), _other.nsb.toCharArray())
+                && Arrays.equals(nsb.toFieldArray(), _other.nsb.toFieldArray())
+                && first.toBigDecimal().equals(_other.first.toBigDecimal())
+                && second.toBigDecimal().equals(_other.second.toBigDecimal());
+    }
+}
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/LocalizedNumberRangeFormatter.java b/icu4j/main/classes/core/src/com/ibm/icu/number/LocalizedNumberRangeFormatter.java
new file mode 100644 (file)
index 0000000..0721ab7
--- /dev/null
@@ -0,0 +1,56 @@
+// © 2018 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package com.ibm.icu.number;
+
+import com.ibm.icu.impl.number.DecimalQuantity;
+import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD;
+import com.ibm.icu.impl.number.NumberStringBuilder;
+import com.ibm.icu.number.FormattedNumberRange.RangeIdentityType;
+import com.ibm.icu.text.NumberFormat;
+
+/**
+ * A NumberRangeFormatter that has a locale associated with it; this means .formatRange() methods are available.
+ *
+ * @author sffc
+ * @draft ICU 63
+ * @provisional This API might change or be removed in a future release.
+ * @see NumberRangeFormatter
+ */
+public class LocalizedNumberRangeFormatter extends NumberRangeFormatterSettings<LocalizedNumberRangeFormatter> {
+
+    LocalizedNumberRangeFormatter(NumberRangeFormatterSettings<?> parent, int key, Object value) {
+        super(parent, key, value);
+    }
+
+    /**
+     * Format the given integers to a string using the settings specified in the NumberRangeFormatter fluent setting
+     * chain.
+     *
+     * @param first
+     *            The first number in the range, usually to the left in LTR locales.
+     * @param second
+     *            The second number in the range, usually to the right in LTR locales.
+     * @return A FormattedNumber object; call .toString() to get the string.
+     * @draft ICU 63
+     * @provisional This API might change or be removed in a future release.
+     * @see NumberRangeFormatter
+     */
+    public FormattedNumberRange formatRange(int first, int second) {
+        // TODO: This is a placeholder implementation.
+        DecimalQuantity dq1 = new DecimalQuantity_DualStorageBCD(first);
+        DecimalQuantity dq2 = new DecimalQuantity_DualStorageBCD(second);
+        NumberStringBuilder nsb = new NumberStringBuilder();
+        nsb.append(dq1.toPlainString(), NumberFormat.Field.INTEGER);
+        nsb.append(" --- ", null);
+        nsb.append(dq2.toPlainString(), NumberFormat.Field.INTEGER);
+        RangeIdentityType identityType = (first == second) ? RangeIdentityType.EQUAL_BEFORE_ROUNDING
+                : RangeIdentityType.NOT_EQUAL;
+        return new FormattedNumberRange(nsb, dq1, dq2, identityType);
+    }
+
+    @Override
+    LocalizedNumberRangeFormatter create(int key, Object value) {
+        return new LocalizedNumberRangeFormatter(this, key, value);
+    }
+
+}
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberRangeFormatter.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberRangeFormatter.java
new file mode 100644 (file)
index 0000000..8c351b3
--- /dev/null
@@ -0,0 +1,19 @@
+// © 2018 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package com.ibm.icu.number;
+
+/**
+ * The main entrypoint to the formatting of ranges of numbers, including currencies and other units of measurement.
+ *
+ * @author sffc
+ * @draft ICU 63
+ * @provisional This API might change or be removed in a future release.
+ * @see NumberFormatter
+ */
+public abstract class NumberRangeFormatter {
+
+    public static enum RangeCollapse {}
+
+    public static enum RangeIdentityFallback {}
+
+}
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberRangeFormatterSettings.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberRangeFormatterSettings.java
new file mode 100644 (file)
index 0000000..d7725e8
--- /dev/null
@@ -0,0 +1,139 @@
+// © 2018 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package com.ibm.icu.number;
+
+import com.ibm.icu.impl.number.range.RangeMacroProps;
+import com.ibm.icu.number.NumberRangeFormatter.RangeCollapse;
+import com.ibm.icu.number.NumberRangeFormatter.RangeIdentityFallback;
+import com.ibm.icu.util.ULocale;
+
+/**
+ * An abstract base class for specifying settings related to number formatting. This class is implemented by
+ * {@link UnlocalizedNumberRangeFormatter} and {@link LocalizedNumberRangeFormatter}. This class is not intended for
+ * public subclassing.
+ *
+ * @author sffc
+ * @draft ICU 63
+ * @provisional This API might change or be removed in a future release.
+ * @see NumberRangeFormatter
+ */
+public abstract class NumberRangeFormatterSettings<T extends NumberRangeFormatterSettings<?>> {
+
+    static final int KEY_MACROS = 0; // not used
+    static final int KEY_LOCALE = 1;
+    static final int KEY_FORMATTER_1 = 2;
+    static final int KEY_FORMATTER_2 = 3;
+    static final int KEY_COLLAPSE = 4;
+    static final int KEY_IDENTITY_FALLBACK = 5;
+    static final int KEY_MAX = 6;
+
+    final NumberRangeFormatterSettings<?> parent;
+    final int key;
+    final Object value;
+    volatile RangeMacroProps resolvedMacros;
+
+    NumberRangeFormatterSettings(NumberRangeFormatterSettings<?> parent, int key, Object value) {
+        this.parent = parent;
+        this.key = key;
+        this.value = value;
+    }
+
+    public T numberFormatter(NumberFormatterSettings<?> formatter) {
+        return numberFormatters(formatter, formatter);
+    }
+
+    public T numberFormatters(NumberFormatterSettings<?> formatterFirst, NumberFormatterSettings<?> formatterSecond) {
+        T intermediate = create(KEY_FORMATTER_1, formatterFirst);
+        return (T) intermediate.create(KEY_FORMATTER_2, formatterSecond);
+    }
+
+    public T collapse(RangeCollapse collapse) {
+        return create(KEY_COLLAPSE, collapse);
+    }
+
+    public T identityFallback(RangeIdentityFallback identityFallback) {
+        return create(KEY_IDENTITY_FALLBACK, identityFallback);
+    }
+
+    /* package-protected */ abstract T create(int key, Object value);
+
+    RangeMacroProps 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.
+        // TODO: Remove the reference to the parent after the macros are resolved?
+        RangeMacroProps macros = new RangeMacroProps();
+        NumberRangeFormatterSettings<?> current = this;
+        while (current != null) {
+            switch (current.key) {
+            case KEY_MACROS:
+                // ignored for now
+                break;
+            case KEY_LOCALE:
+                if (macros.loc == null) {
+                    macros.loc = (ULocale) current.value;
+                }
+                break;
+            case KEY_FORMATTER_1:
+                if (macros.formatter1 == null) {
+                    macros.formatter1 = (NumberFormatterSettings<?>) current.value;
+                }
+                break;
+            case KEY_FORMATTER_2:
+                if (macros.formatter2 == null) {
+                    macros.formatter2 = (NumberFormatterSettings<?>) current.value;
+                }
+                break;
+            case KEY_COLLAPSE:
+                if (macros.collapse == null) {
+                    macros.collapse = (RangeCollapse) current.value;
+                }
+                break;
+            case KEY_IDENTITY_FALLBACK:
+                if (macros.identityFallback == null) {
+                    macros.identityFallback = (RangeIdentityFallback) current.value;
+                }
+                break;
+            default:
+                throw new AssertionError("Unknown key: " + current.key);
+            }
+            current = current.parent;
+        }
+        resolvedMacros = macros;
+        return macros;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @draft ICU 60
+     * @provisional This API might change or be removed in a future release.
+     */
+    @Override
+    public int hashCode() {
+        return resolve().hashCode();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @draft ICU 60
+     * @provisional This API might change or be removed in a future release.
+     */
+    @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());
+    }
+}
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/UnlocalizedNumberRangeFormatter.java b/icu4j/main/classes/core/src/com/ibm/icu/number/UnlocalizedNumberRangeFormatter.java
new file mode 100644 (file)
index 0000000..c3ee21e
--- /dev/null
@@ -0,0 +1,67 @@
+// © 2018 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package com.ibm.icu.number;
+
+import java.util.Locale;
+
+import com.ibm.icu.util.ULocale;
+
+/**
+ * A NumberRangeFormatter that does not yet have a locale. In order to format, a locale must be specified.
+ *
+ * @author sffc
+ * @draft ICU 63
+ * @provisional This API might change or be removed in a future release.
+ * @see NumberRangeFormatter
+ */
+public class UnlocalizedNumberRangeFormatter extends NumberRangeFormatterSettings<UnlocalizedNumberRangeFormatter> {
+
+    /** Base constructor; called during startup only. */
+    UnlocalizedNumberRangeFormatter() {
+        super(null, KEY_MACROS, null);
+    }
+
+    UnlocalizedNumberRangeFormatter(NumberRangeFormatterSettings<?> parent, int key, Object value) {
+        super(parent, key, value);
+    }
+
+    /**
+     * Associate the given locale with the number range formatter. The locale is used for picking the
+     * appropriate symbols, formats, and other data for number display.
+     *
+     * <p>
+     * To use the Java default locale, call Locale.getDefault():
+     *
+     * <pre>
+     * NumberFormatter.with(). ... .locale(Locale.getDefault())
+     * </pre>
+     *
+     * @param locale
+     *            The locale to use when loading data for number range formatting.
+     * @return The fluent chain
+     * @draft ICU 63
+     * @provisional This API might change or be removed in a future release.
+     */
+    public LocalizedNumberRangeFormatter locale(Locale locale) {
+        return new LocalizedNumberRangeFormatter(this, KEY_LOCALE, ULocale.forLocale(locale));
+    }
+
+    /**
+     * ULocale version of the {@link #locale(Locale)} setter above.
+     *
+     * @param locale
+     *            The locale to use when loading data for number range formatting.
+     * @return The fluent chain
+     * @see #locale(Locale)
+     * @draft ICU 63
+     * @provisional This API might change or be removed in a future release.
+     */
+    public LocalizedNumberRangeFormatter locale(ULocale locale) {
+        return new LocalizedNumberRangeFormatter(this, KEY_LOCALE, locale);
+    }
+
+    @Override
+    UnlocalizedNumberRangeFormatter create(int key, Object value) {
+        return new UnlocalizedNumberRangeFormatter(this, key, value);
+    }
+}