From: Shane Carr Date: Wed, 28 Feb 2018 09:29:33 +0000 (+0000) Subject: ICU-8610 Adds skeleton support for measure units. X-Git-Tag: release-62-rc~200^2~111 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=59e4fc517279d50c8269520c551931f5df69cd2e;p=icu ICU-8610 Adds skeleton support for measure units. X-SVN-Rev: 41014 --- diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/StringSegment.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/StringSegment.java index 5a1f6bd62c7..750ad1d4bdd 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/StringSegment.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/StringSegment.java @@ -81,9 +81,7 @@ public class StringSegment implements CharSequence { @Override public CharSequence subSequence(int start, int end) { - throw new AssertionError(); // Never used - // Possible implementation: - // return str.subSequence(start + this.start, end + this.start); + return str.subSequence(start + this.start, end + this.start); } /** diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java index 3eaef0978b4..1841ad700c8 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java @@ -6,12 +6,17 @@ import java.math.BigDecimal; import java.math.RoundingMode; import java.util.HashMap; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import com.ibm.icu.impl.PatternProps; import com.ibm.icu.impl.StringSegment; import com.ibm.icu.impl.number.MacroProps; +import com.ibm.icu.number.NumberFormatter.UnitWidth; +import com.ibm.icu.util.Currency; import com.ibm.icu.util.Currency.CurrencyUsage; +import com.ibm.icu.util.MeasureUnit; +import com.ibm.icu.util.NoUnit; /** * @author sffc @@ -20,7 +25,7 @@ import com.ibm.icu.util.Currency.CurrencyUsage; class NumberSkeletonImpl { static enum StemType { - ROUNDER, FRACTION_ROUNDER, MAYBE_INCREMENT_ROUNDER, CURRENCY_ROUNDER + OTHER, ROUNDER, FRACTION_ROUNDER, MAYBE_INCREMENT_ROUNDER, CURRENCY_ROUNDER, MEASURE_UNIT, UNIT_WIDTH } static class SkeletonDataStructure { @@ -62,6 +67,12 @@ class NumberSkeletonImpl { "round-currency-standard", Rounder.currency(CurrencyUsage.STANDARD)); skeletonData.put(StemType.ROUNDER, "round-currency-cash", Rounder.currency(CurrencyUsage.CASH)); + + skeletonData.put(StemType.UNIT_WIDTH, "unit-width-narrow", UnitWidth.NARROW); + skeletonData.put(StemType.UNIT_WIDTH, "unit-width-short", UnitWidth.SHORT); + skeletonData.put(StemType.UNIT_WIDTH, "unit-width-full-name", UnitWidth.FULL_NAME); + skeletonData.put(StemType.UNIT_WIDTH, "unit-width-iso-code", UnitWidth.ISO_CODE); + skeletonData.put(StemType.UNIT_WIDTH, "unit-width-hidden", UnitWidth.HIDDEN); } private static final Map cache = new ConcurrentHashMap(); @@ -171,6 +182,7 @@ class NumberSkeletonImpl { // Check for stems that require an option switch (stem) { case MAYBE_INCREMENT_ROUNDER: + case MEASURE_UNIT: throw new SkeletonSyntaxException("Stem requires an option", segment); default: break; @@ -192,6 +204,10 @@ class NumberSkeletonImpl { checkNull(macros.rounder, content); macros.rounder = (Rounder) value; break; + case UNIT_WIDTH: + checkNull(macros.unitWidth, content); + macros.unitWidth = (UnitWidth) value; + break; default: assert false; } @@ -201,6 +217,8 @@ class NumberSkeletonImpl { // Second try: literal stems that require an option if (content.equals("round-increment")) { return StemType.MAYBE_INCREMENT_ROUNDER; + } else if (content.equals("measure-unit")) { + return StemType.MEASURE_UNIT; } // Second try: stem "blueprint" syntax @@ -249,6 +267,14 @@ class NumberSkeletonImpl { } } + // Measure unit option + switch (stem) { + case MEASURE_UNIT: + // The measure unit option is required. + parseMeasureUnitOption(content, macros); + return StemType.OTHER; + } + // Unknown option throw new SkeletonSyntaxException("Unknown option", content); } @@ -260,6 +286,14 @@ class NumberSkeletonImpl { generateRoundingValue(macros, sb); sb.append(' '); } + if (macros.unit != null) { + generateUnitValue(macros, sb); + sb.append(' '); + } + if (macros.unitWidth != null) { + generateUnitWidthValue(macros, sb); + sb.append(' '); + } // Remove the trailing space if (sb.length() > 0) { @@ -390,15 +424,11 @@ class NumberSkeletonImpl { } private static void parseIncrementOption(CharSequence content, MacroProps macros) { - // Clunkilly convert the CharSequence to a char array for the BigDecimal constructor. - // We can't use content.toString() because that doesn't create a clean string. - char[] chars = new char[content.length()]; - for (int i = 0; i < content.length(); i++) { - chars[i] = content.charAt(i); - } + // Call content.subSequence() because content.toString() doesn't create a clean string. + String str = content.subSequence(0, content.length()).toString(); BigDecimal increment; try { - increment = new BigDecimal(chars); + increment = new BigDecimal(str); } catch (NumberFormatException e) { throw new SkeletonSyntaxException("Invalid rounding increment", content, e); } @@ -425,6 +455,29 @@ class NumberSkeletonImpl { sb.append(mode.toString()); } + private static void parseMeasureUnitOption(CharSequence content, MacroProps macros) { + // NOTE: The category (type) of the unit is guaranteed to be a valid subtag (alphanumeric) + // http://unicode.org/reports/tr35/#Validity_Data + int firstHyphen = 0; + while (firstHyphen < content.length() && content.charAt(firstHyphen) != '-') { + firstHyphen++; + } + String type = content.subSequence(0, firstHyphen).toString(); + String subType = content.subSequence(firstHyphen + 1, content.length()).toString(); + Set units = MeasureUnit.getAvailable(type); + for (MeasureUnit unit : units) { + if (subType.equals(unit.getSubtype())) { + macros.unit = unit; + return; + } + } + throw new SkeletonSyntaxException("Unknown unit", content); + } + + private static void generateMeasureUnitOption(MeasureUnit unit, StringBuilder sb) { + sb.append(unit.getType() + "-" + unit.getSubtype()); + } + ///// private static void generateRoundingValue(MacroProps macros, StringBuilder sb) { @@ -474,6 +527,32 @@ class NumberSkeletonImpl { } } + private static void generateUnitValue(MacroProps macros, StringBuilder sb) { + // Check for literals + String literal = skeletonData.valueToStem(macros.unit); + if (literal != null) { + sb.append(literal); + return; + } + + // Generate the stem + if (macros.unit instanceof Currency) { + // TODO + } else if (macros.unit instanceof NoUnit) { + // TODO + } else { + sb.append("measure-unit/"); + generateMeasureUnitOption(macros.unit, sb); + } + } + + private static void generateUnitWidthValue(MacroProps macros, StringBuilder sb) { + // There should be a literal. + String literal = skeletonData.valueToStem(macros.unitWidth); + assert literal != null; + sb.append(literal); + } + ///// private static void checkNull(Object value, CharSequence content) { diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java index d6c8a8c5524..49569d73d4b 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java @@ -377,7 +377,7 @@ public class NumberFormatterApiTest { public void unitMeasure() { assertFormatDescending( "Meters Short", - "U:length:meter", + "measure-unit/length-meter", NumberFormatter.with().unit(MeasureUnit.METER), ULocale.ENGLISH, "87,650 m", @@ -392,7 +392,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Meters Long", - "U:length:meter unit-width=FULL_NAME", + "measure-unit/length-meter unit-width-full-name", NumberFormatter.with().unit(MeasureUnit.METER).unitWidth(UnitWidth.FULL_NAME), ULocale.ENGLISH, "87,650 meters", @@ -407,7 +407,7 @@ public class NumberFormatterApiTest { assertFormatDescending( "Compact Meters Long", - "CC U:length:meter unit-width=FULL_NAME", + "compact-long measure-unit/length-meter unit-width-full-name", NumberFormatter.with().notation(Notation.compactLong()).unit(MeasureUnit.METER) .unitWidth(UnitWidth.FULL_NAME), ULocale.ENGLISH,