ICU-11276 Adding initial Java NumberRangeFormatter boilerplate.

This commit is contained in:
Shane Carr 2018-08-28 19:50:55 -07:00
parent cad261fdca
commit 553f22585d
No known key found for this signature in database
GPG Key ID: FCED3B24AAB18B5C
6 changed files with 539 additions and 0 deletions

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

@ -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 {}
}

View File

@ -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());
}
}

View File

@ -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);
}
}