ICU-20941 Support formatting joule-per-furlong (builtin-per-builtin).

See #1409
This commit is contained in:
Hugo van der Merwe 2020-11-04 01:08:48 +00:00
parent 7c8f857da8
commit 710fa5aaf9
11 changed files with 103 additions and 159 deletions

View File

@ -389,8 +389,12 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe,
fMixedUnitLongNameHandler.getAlias(), status);
chain = fMixedUnitLongNameHandler.getAlias();
} else {
MeasureUnit unit = macros.unit;
if (!utils::unitIsBaseUnit(macros.perUnit)) {
unit = unit.product(macros.perUnit.reciprocal(status), status);
}
fLongNameHandler.adoptInsteadAndCheckErrorCode(new LongNameHandler(), status);
LongNameHandler::forMeasureUnit(macros.locale, macros.unit, macros.perUnit, unitWidth,
LongNameHandler::forMeasureUnit(macros.locale, unit, unitWidth,
resolvePluralRules(macros.rules, macros.locale, status),
chain, fLongNameHandler.getAlias(), status);
chain = fLongNameHandler.getAlias();

View File

@ -218,38 +218,38 @@ UnicodeString getPerUnitFormat(const Locale& locale, const UNumberUnitWidth &wid
} // namespace
void LongNameHandler::forMeasureUnit(const Locale &loc, const MeasureUnit &unitRef,
const MeasureUnit &perUnit, const UNumberUnitWidth &width,
const PluralRules *rules, const MicroPropsGenerator *parent,
LongNameHandler *fillIn, UErrorCode &status) {
const UNumberUnitWidth &width, const PluralRules *rules,
const MicroPropsGenerator *parent, LongNameHandler *fillIn,
UErrorCode &status) {
// Not valid for mixed units that aren't built-in units, and there should
// not be any built-in mixed units!
U_ASSERT(uprv_strcmp(unitRef.getType(), "") != 0 ||
unitRef.getComplexity(status) != UMEASURE_UNIT_MIXED);
U_ASSERT(fillIn != nullptr);
MeasureUnit unit = unitRef;
if (uprv_strcmp(perUnit.getType(), "none") != 0) {
// Compound unit: first try to simplify (e.g., meters per second is its own unit).
MeasureUnit simplified = unit.product(perUnit.reciprocal(status), status);
if (uprv_strcmp(simplified.getType(), "") != 0) {
unit = simplified;
} else {
// No simplified form is available.
forCompoundUnit(loc, unit, perUnit, width, rules, parent, fillIn, status);
return;
if (uprv_strcmp(unitRef.getType(), "") == 0) {
// Not a built-in unit. Split it up, since we can already format
// "builtin-per-builtin".
// TODO(ICU-20941): support more generic case than builtin-per-builtin.
MeasureUnitImpl fullUnit = MeasureUnitImpl::forMeasureUnitMaybeCopy(unitRef, status);
MeasureUnitImpl unit;
MeasureUnitImpl perUnit;
for (int32_t i = 0; i < fullUnit.units.length(); i++) {
SingleUnitImpl *subUnit = fullUnit.units[i];
if (subUnit->dimensionality > 0) {
unit.append(*subUnit, status);
} else {
subUnit->dimensionality *= -1;
perUnit.append(*subUnit, status);
}
}
}
if (uprv_strcmp(unit.getType(), "") == 0) {
// TODO(ICU-20941): Unsanctioned unit. Not yet fully supported. Set an
// error code. Once we support not-built-in units here, unitRef may be
// anything, but if not built-in, perUnit has to be "none".
status = U_UNSUPPORTED_ERROR;
forCompoundUnit(loc, std::move(unit).build(status), std::move(perUnit).build(status), width,
rules, parent, fillIn, status);
return;
}
UnicodeString simpleFormats[ARRAY_LENGTH];
getMeasureData(loc, unit, width, simpleFormats, status);
getMeasureData(loc, unitRef, width, simpleFormats, status);
if (U_FAILURE(status)) {
return;
}
@ -574,7 +574,7 @@ LongNameMultiplexer::forMeasureUnits(const Locale &loc, const MaybeStackVector<M
result->fHandlers[i] = mlnh;
} else {
LongNameHandler *lnh = result->fLongNameHandlers.createAndCheckErrorCode(status);
LongNameHandler::forMeasureUnit(loc, unit, MeasureUnit(), width, rules, NULL, lnh, status);
LongNameHandler::forMeasureUnit(loc, unit, width, rules, NULL, lnh, status);
result->fHandlers[i] = lnh;
}
if (U_FAILURE(status)) {

View File

@ -38,9 +38,6 @@ class LongNameHandler : public MicroPropsGenerator, public ModifierStore, public
/**
* Construct a localized LongNameHandler for the specified MeasureUnit.
*
* Compound units can be constructed via `unit` and `perUnit`. Both of these
* must then be built-in units.
*
* Mixed units are not supported, use MixedUnitLongNameHandler::forMeasureUnit.
*
* This function uses a fillIn intead of returning a pointer, because we
@ -48,15 +45,13 @@ class LongNameHandler : public MicroPropsGenerator, public ModifierStore, public
* didn't create itself).
*
* @param loc The desired locale.
* @param unit The measure unit to construct a LongNameHandler for. If
* `perUnit` is also defined, `unit` must not be a mixed unit.
* @param perUnit If `unit` is a mixed unit, `perUnit` must be "none".
* @param unitRef The measure unit to construct a LongNameHandler for.
* @param width Specifies the desired unit rendering.
* @param rules Does not take ownership.
* @param parent Does not take ownership.
* @param fillIn Required.
*/
static void forMeasureUnit(const Locale &loc, const MeasureUnit &unit, const MeasureUnit &perUnit,
static void forMeasureUnit(const Locale &loc, const MeasureUnit &unitRef,
const UNumberUnitWidth &width, const PluralRules *rules,
const MicroPropsGenerator *parent, LongNameHandler *fillIn,
UErrorCode &status);

View File

@ -1040,37 +1040,12 @@ void blueprint_helpers::parseIdentifierUnitOption(const StringSegment& segment,
SKELETON_UCHAR_TO_CHAR(buffer, segment.toTempUnicodeString(), 0, segment.length(), status);
ErrorCode internalStatus;
auto fullUnit = MeasureUnitImpl::forIdentifier(buffer.toStringPiece(), internalStatus);
macros.unit = MeasureUnit::forIdentifier(buffer.toStringPiece(), internalStatus);
if (internalStatus.isFailure()) {
// throw new SkeletonSyntaxException("Invalid core unit identifier", segment, e);
status = U_NUMBER_SKELETON_SYNTAX_ERROR;
return;
}
// Mixed units can only be represented by full MeasureUnit instances, so we
// don't split the denominator into macros.perUnit.
if (fullUnit.complexity == UMEASURE_UNIT_MIXED) {
macros.unit = std::move(fullUnit).build(status);
return;
}
// When we have a built-in unit (e.g. meter-per-second), we don't split it up
MeasureUnit testBuiltin = fullUnit.copy(status).build(status);
if (uprv_strcmp(testBuiltin.getType(), "") != 0) {
macros.unit = std::move(testBuiltin);
return;
}
// TODO(ICU-20941): Clean this up.
for (int32_t i = 0; i < fullUnit.units.length(); i++) {
SingleUnitImpl* subUnit = fullUnit.units[i];
if (subUnit->dimensionality > 0) {
macros.unit = macros.unit.product(subUnit->build(status), status);
} else {
subUnit->dimensionality *= -1;
macros.perUnit = macros.perUnit.product(subUnit->build(status), status);
}
}
}
void blueprint_helpers::parseUnitUsageOption(const StringSegment &segment, MacroProps &macros,

View File

@ -867,22 +867,21 @@ void NumberFormatterApiTest::unitCompoundMeasure() {
u"0.008765 J/fur",
u"0 J/fur");
// TODO(ICU-20941): Support constructions such as this one.
// assertFormatDescending(
// u"Joules Per Furlong Short with unit identifier via API",
// u"measure-unit/energy-joule per-measure-unit/length-furlong",
// u"unit/joule-per-furlong",
// NumberFormatter::with().unit(MeasureUnit::forIdentifier("joule-per-furlong", status)),
// Locale::getEnglish(),
// u"87,650 J/fur",
// u"8,765 J/fur",
// u"876.5 J/fur",
// u"87.65 J/fur",
// u"8.765 J/fur",
// u"0.8765 J/fur",
// u"0.08765 J/fur",
// u"0.008765 J/fur",
// u"0 J/fur");
assertFormatDescending(
u"Joules Per Furlong Short with unit identifier via API",
u"measure-unit/energy-joule per-measure-unit/length-furlong",
u"unit/joule-per-furlong",
NumberFormatter::with().unit(MeasureUnit::forIdentifier("joule-per-furlong", status)),
Locale::getEnglish(),
u"87,650 J/fur",
u"8,765 J/fur",
u"876.5 J/fur",
u"87.65 J/fur",
u"8.765 J/fur",
u"0.8765 J/fur",
u"0.08765 J/fur",
u"0.008765 J/fur",
u"0 J/fur");
assertFormatDescending(
u"Pounds per Square Inch: composed",

View File

@ -13,6 +13,8 @@ import com.ibm.icu.impl.SimpleFormatterImpl;
import com.ibm.icu.impl.StandardPlural;
import com.ibm.icu.impl.UResource;
import com.ibm.icu.impl.number.Modifier.Signum;
import com.ibm.icu.impl.units.MeasureUnitImpl;
import com.ibm.icu.impl.units.SingleUnitImpl;
import com.ibm.icu.number.NumberFormatter.UnitWidth;
import com.ibm.icu.text.NumberFormat;
import com.ibm.icu.text.PluralRules;
@ -195,15 +197,10 @@ public class LongNameHandler
/**
* Construct a localized LongNameHandler for the specified MeasureUnit.
* <p>
* Compound units can be constructed via `unit` and `perUnit`. Both of these
* must then be built-in units.
* <p>
* Mixed units are not supported, use MixedUnitLongNameHandler.forMeasureUnit.
*
* @param locale The desired locale.
* @param unit The measure unit to construct a LongNameHandler for. If
* `perUnit` is also defined, `unit` must not be a mixed unit.
* @param perUnit If `unit` is a mixed unit, `perUnit` must be null.
* @param unit The measure unit to construct a LongNameHandler for.
* @param width Specifies the desired unit rendering.
* @param rules Plural rules.
* @param parent Plural rules.
@ -211,25 +208,34 @@ public class LongNameHandler
public static LongNameHandler forMeasureUnit(
ULocale locale,
MeasureUnit unit,
MeasureUnit perUnit,
UnitWidth width,
PluralRules rules,
MicroPropsGenerator parent) {
if (perUnit != null) {
// Compound unit: first try to simplify (e.g., meters per second is its own unit).
MeasureUnit simplified = unit.product(perUnit.reciprocal());
if (simplified.getType() != null) {
unit = simplified;
} else {
// No simplified form is available.
return forCompoundUnit(locale, unit, perUnit, width, rules, parent);
}
}
if (unit.getType() == null) {
// TODO(ICU-20941): Unsanctioned unit. Not yet fully supported.
throw new UnsupportedOperationException("Unsanctioned unit, not yet supported: " +
unit.getIdentifier());
// Not a built-in unit. Split it up, since we can already format
// "builtin-per-builtin".
// TODO(ICU-20941): support more generic case than builtin-per-builtin.
MeasureUnitImpl fullUnit = unit.getCopyOfMeasureUnitImpl();
unit = null;
MeasureUnit perUnit = null;
for (SingleUnitImpl subUnit : fullUnit.getSingleUnits()) {
if (subUnit.getDimensionality() > 0) {
if (unit == null) {
unit = subUnit.build();
} else {
unit = unit.product(subUnit.build());
}
} else {
// It's okay to mutate fullUnit, we made a temporary copy:
subUnit.setDimensionality(subUnit.getDimensionality() * -1);
if (perUnit == null) {
perUnit = subUnit.build();
} else {
perUnit = perUnit.product(subUnit.build());
}
}
}
return forCompoundUnit(locale, unit, perUnit, width, rules, parent);
}
String[] simpleFormats = new String[ARRAY_LENGTH];
@ -336,6 +342,7 @@ public class LongNameHandler
* Does not call parent.processQuantity, so cannot get a MicroProps instance
* that way. Instead, the instance is passed in as a parameter.
*/
@Override
public MicroProps processQuantityWithMicros(DecimalQuantity quantity, MicroProps micros) {
StandardPlural pluralForm = RoundingUtils.getPluralSafe(micros.rounder, rules, quantity);
micros.modOuter = modifiers.get(pluralForm);

View File

@ -8,7 +8,6 @@ import java.util.List;
import com.ibm.icu.number.NumberFormatter;
import com.ibm.icu.text.PluralRules;
import com.ibm.icu.util.MeasureUnit;
import com.ibm.icu.util.NoUnit;
import com.ibm.icu.util.ULocale;
/**
@ -64,8 +63,7 @@ public class LongNameMultiplexer implements MicroPropsGenerator {
.forMeasureUnit(locale, unit, width, rules, null);
result.fHandlers.add(mlnh);
} else {
LongNameHandler lnh = LongNameHandler
.forMeasureUnit(locale, unit, NoUnit.BASE, width, rules, null);
LongNameHandler lnh = LongNameHandler.forMeasureUnit(locale, unit, width, rules, null);
result.fHandlers.add(lnh);
}
}

View File

@ -400,12 +400,11 @@ class NumberFormatterImpl {
pluralRules,
chain);
} else {
chain = LongNameHandler.forMeasureUnit(macros.loc,
macros.unit,
macros.perUnit,
unitWidth,
pluralRules,
chain);
MeasureUnit unit = macros.unit;
if (macros.perUnit != null) {
unit = unit.product(macros.perUnit.reciprocal());
}
chain = LongNameHandler.forMeasureUnit(macros.loc, unit, unitWidth, pluralRules, chain);
}
} else if (isCurrency && unitWidth == UnitWidth.FULL_NAME) {
if (rules == null) {

View File

@ -12,8 +12,6 @@ import com.ibm.icu.impl.SoftCache;
import com.ibm.icu.impl.StringSegment;
import com.ibm.icu.impl.number.MacroProps;
import com.ibm.icu.impl.number.RoundingUtils;
import com.ibm.icu.impl.units.MeasureUnitImpl;
import com.ibm.icu.impl.units.SingleUnitImpl;
import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay;
import com.ibm.icu.number.NumberFormatter.GroupingStrategy;
import com.ibm.icu.number.NumberFormatter.SignDisplay;
@ -1063,45 +1061,11 @@ class NumberSkeletonImpl {
* specified via a "unit/" concise skeleton.
*/
private static void parseIdentifierUnitOption(StringSegment segment, MacroProps macros) {
MeasureUnitImpl fullUnit;
try {
fullUnit = MeasureUnitImpl.forIdentifier(segment.asString());
macros.unit = MeasureUnit.forIdentifier(segment.asString());
} catch (IllegalArgumentException e) {
throw new SkeletonSyntaxException("Invalid unit stem", segment);
}
// Mixed units can only be represented by full MeasureUnit instances, so we
// don't split the denominator into macros.perUnit.
if (fullUnit.getComplexity() == MeasureUnit.Complexity.MIXED) {
macros.unit = fullUnit.build();
return;
}
// When we have a built-in unit (e.g. meter-per-second), we don't split it up
MeasureUnit testBuiltin = fullUnit.build();
if (testBuiltin.getType() != null) {
macros.unit = testBuiltin;
return;
}
// TODO(ICU-20941): Clean this up.
for (SingleUnitImpl subUnit : fullUnit.getSingleUnits()) {
if (subUnit.getDimensionality() > 0) {
if (macros.unit == null) {
macros.unit = subUnit.build();
} else {
macros.unit = macros.unit.product(subUnit.build());
}
} else {
// It's okay to mutate fullUnit, we're throwing it away after this:
subUnit.setDimensionality(subUnit.getDimensionality() * -1);
if (macros.perUnit == null) {
macros.perUnit = subUnit.build();
} else {
macros.perUnit = macros.perUnit.product(subUnit.build());
}
}
}
}
private static void parseUnitUsageOption(StringSegment segment, MacroProps macros) {

View File

@ -542,7 +542,7 @@ public class MeasureUnit implements Serializable {
return implCopy.build();
}
final MeasureUnitImpl otherImplRef = other.getMayBeReferenceOfMeasureUnitImpl();
final MeasureUnitImpl otherImplRef = other.getMaybeReferenceOfMeasureUnitImpl();
if (implCopy.getComplexity() == Complexity.MIXED || otherImplRef.getComplexity() == Complexity.MIXED) {
throw new UnsupportedOperationException();
}
@ -571,7 +571,8 @@ public class MeasureUnit implements Serializable {
* @provisional This API might change or be removed in a future release.
*/
public List<MeasureUnit> splitToSingleUnits() {
final ArrayList<SingleUnitImpl> singleUnits = getMayBeReferenceOfMeasureUnitImpl().getSingleUnits();
final ArrayList<SingleUnitImpl> singleUnits =
getMaybeReferenceOfMeasureUnitImpl().getSingleUnits();
List<MeasureUnit> result = new ArrayList<>(singleUnits.size());
for (SingleUnitImpl singleUnit : singleUnits) {
result.add(singleUnit.build());
@ -1992,8 +1993,11 @@ public class MeasureUnit implements Serializable {
/**
*
* @return this object in a MeasureUnitImpl form.
* @internal
* @deprecated This API is ICU internal only.
*/
private MeasureUnitImpl getCopyOfMeasureUnitImpl() {
@Deprecated
public MeasureUnitImpl getCopyOfMeasureUnitImpl() {
return this.measureUnitImpl == null ?
MeasureUnitImpl.forIdentifier(getIdentifier()) :
this.measureUnitImpl.copy();
@ -2003,7 +2007,7 @@ public class MeasureUnit implements Serializable {
*
* @return this object in a MeasureUnitImpl form.
*/
private MeasureUnitImpl getMayBeReferenceOfMeasureUnitImpl(){
private MeasureUnitImpl getMaybeReferenceOfMeasureUnitImpl() {
return this.measureUnitImpl == null ?
MeasureUnitImpl.forIdentifier(getIdentifier()) :
this.measureUnitImpl;

View File

@ -824,22 +824,21 @@ public class NumberFormatterApiTest extends TestFmwk {
"0.008765 J/fur",
"0 J/fur");
// // TODO(ICU-20941): Support constructions such as this one.
// assertFormatDescending(
// "Joules Per Furlong Short with unit identifier via API",
// "measure-unit/energy-joule per-measure-unit/length-furlong",
// "unit/joule-per-furlong",
// NumberFormatter.with().unit(MeasureUnit.forIdentifier("joule-per-furlong")),
// ULocale.ENGLISH,
// "87,650 J/fur",
// "8,765 J/fur",
// "876.5 J/fur",
// "87.65 J/fur",
// "8.765 J/fur",
// "0.8765 J/fur",
// "0.08765 J/fur",
// "0.008765 J/fur",
// "0 J/fur");
assertFormatDescending(
"Joules Per Furlong Short with unit identifier via API",
"measure-unit/energy-joule per-measure-unit/length-furlong",
"unit/joule-per-furlong",
NumberFormatter.with().unit(MeasureUnit.forIdentifier("joule-per-furlong")),
ULocale.ENGLISH,
"87,650 J/fur",
"8,765 J/fur",
"876.5 J/fur",
"87.65 J/fur",
"8.765 J/fur",
"0.8765 J/fur",
"0.08765 J/fur",
"0.008765 J/fur",
"0 J/fur");
assertFormatDescending(
"Pounds per Square Inch: composed",