ICU-11276 Adding initial Java NumberRangeFormatter boilerplate.
This commit is contained in:
parent
cad261fdca
commit
553f22585d
@ -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);
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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 {}
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user