From 36c6e6c6a2710868adea51c3875f90973c71cdb1 Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Fri, 12 May 2017 21:50:31 +0000 Subject: [PATCH] ICU-13073 Adding API for setting custom compact data, for CLDR Survey Tool. X-SVN-Rev: 40117 --- .../com/ibm/icu/impl/number/Properties.java | 21 +++++- .../formatters/CompactDecimalFormat.java | 70 ++++++++++++++++++- .../test/format/CompactDecimalFormatTest.java | 25 +++++++ .../icu/dev/test/number/PropertiesTest.java | 16 +++++ 4 files changed, 129 insertions(+), 3 deletions(-) diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Properties.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Properties.java index d7e0c04c8b..948fc8eb59 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Properties.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Properties.java @@ -12,6 +12,7 @@ import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; import java.util.ArrayList; +import java.util.Map; import com.ibm.icu.impl.number.Parse.GroupingMode; import com.ibm.icu.impl.number.Parse.ParseMode; @@ -73,6 +74,7 @@ public class Properties /| or #equals(), but it will NOT catch if you forget to add it to #hashCode(). |/ /+--------------------------------------------------------------------------------------------*/ + private transient Map> compactCustomData; private transient CompactStyle compactStyle; private transient Currency currency; private transient CurrencyPluralInfo currencyPluralInfo; @@ -132,6 +134,7 @@ public class Properties } private Properties _clear() { + compactCustomData = DEFAULT_COMPACT_CUSTOM_DATA; compactStyle = DEFAULT_COMPACT_STYLE; currency = DEFAULT_CURRENCY; currencyPluralInfo = DEFAULT_CURRENCY_PLURAL_INFO; @@ -180,6 +183,7 @@ public class Properties } private Properties _copyFrom(Properties other) { + compactCustomData = other.compactCustomData; compactStyle = other.compactStyle; currency = other.currency; currencyPluralInfo = other.currencyPluralInfo; @@ -229,6 +233,7 @@ public class Properties private boolean _equals(Properties other) { boolean eq = true; + eq = eq && _equalsHelper(compactCustomData, other.compactCustomData); eq = eq && _equalsHelper(compactStyle, other.compactStyle); eq = eq && _equalsHelper(currency, other.currency); eq = eq && _equalsHelper(currencyPluralInfo, other.currencyPluralInfo); @@ -292,6 +297,7 @@ public class Properties private int _hashCode() { int hashCode = 0; + hashCode ^= _hashCodeHelper(compactCustomData); hashCode ^= _hashCodeHelper(compactStyle); hashCode ^= _hashCodeHelper(currency); hashCode ^= _hashCodeHelper(currencyPluralInfo); @@ -386,6 +392,13 @@ public class Properties return _equals((Properties) other); } + /// BEGIN GETTERS/SETTERS /// + + @Override + public Map> getCompactCustomData() { + return compactCustomData; + } + @Override public CompactStyle getCompactStyle() { return compactStyle; @@ -396,8 +409,6 @@ public class Properties return currency; } - /// BEGIN GETTERS/SETTERS /// - @Override @Deprecated public CurrencyPluralInfo getCurrencyPluralInfo() { @@ -660,6 +671,12 @@ public class Properties } } + @Override + public Properties setCompactCustomData(Map> compactCustomData) { + this.compactCustomData = compactCustomData; + return this; + } + @Override public Properties setCompactStyle(CompactStyle compactStyle) { this.compactStyle = compactStyle; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/formatters/CompactDecimalFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/formatters/CompactDecimalFormat.java index 2c241e9937..863099c583 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/formatters/CompactDecimalFormat.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/formatters/CompactDecimalFormat.java @@ -49,6 +49,39 @@ public class CompactDecimalFormat extends Format.BeforeFormat { * @return The property bag, for chaining. */ public IProperties setCompactStyle(CompactStyle compactStyle); + + static Map> DEFAULT_COMPACT_CUSTOM_DATA = null; + + /** @see #setCompactCustomData */ + public Map> getCompactCustomData(); + + /** + * Specifies custom data to be used instead of CLDR data when constructing a + * CompactDecimalFormat. The argument should be a map with the following structure: + * + *
+     * {
+     *   "1000": {
+     *     "one": "0 thousand",
+     *     "other": "0 thousand"
+     *   },
+     *   "10000": {
+     *     "one": "00 thousand",
+     *     "other": "00 thousand"
+     *   },
+     *   // ...
+     * }
+     * 
+ * + * This API endpoint is used by the CLDR Survey Tool. + * + * @param compactCustomData A map with the above structure. + * @return The property bag, for chaining. + * @internal + * @deprecated This API is ICU internal only. + */ + @Deprecated + public IProperties setCompactCustomData(Map> compactCustomData); } public static boolean useCompactDecimalFormat(IProperties properties) { @@ -179,7 +212,12 @@ public class CompactDecimalFormat extends Format.BeforeFormat { private CompactDecimalFormat(DecimalFormatSymbols symbols, IProperties properties) { CompactDecimalFingerprint fingerprint = new CompactDecimalFingerprint(symbols, properties); this.rounder = getRounder(properties); - this.data = getData(symbols, fingerprint); + // Short-circuit and use custom data if provided + if (properties.getCompactCustomData() != null) { + this.data = createDataFromCustom(symbols, fingerprint, properties.getCompactCustomData()); + } else { + this.data = getData(symbols, fingerprint); + } this.defaultMod = getDefaultMod(symbols, fingerprint); this.style = properties.getCompactStyle(); // for exporting only } @@ -492,4 +530,34 @@ public class CompactDecimalFormat extends Format.BeforeFormat { } } } + + /** + * Uses data from the custom powersToPluralsToPatterns map instead of an ICUResourceBundle to + * populate an instance of CompactDecimalData. + */ + static CompactDecimalData createDataFromCustom( + DecimalFormatSymbols symbols, + CompactDecimalFingerprint fingerprint, + Map> powersToPluralsToPatterns) { + CompactDecimalData data = new CompactDecimalData(); + PNAffixGenerator pnag = PNAffixGenerator.getThreadLocalInstance(); + for (Map.Entry> magnitudeEntry : + powersToPluralsToPatterns.entrySet()) { + byte magnitude = (byte) (magnitudeEntry.getKey().length() - 1); + for (Map.Entry pluralEntry : magnitudeEntry.getValue().entrySet()) { + StandardPlural plural = StandardPlural.fromString(pluralEntry.getKey().toString()); + String patternString = pluralEntry.getValue().toString(); + Properties properties = PatternString.parseToProperties(patternString); + byte _multiplier = (byte) -(magnitude - properties.getMinimumIntegerDigits() + 1); + if (_multiplier != data.setOrGetMultiplier(magnitude, _multiplier)) { + throw new IllegalArgumentException( + "Different number of zeros for same power of ten in custom compact decimal format data"); + } + PNAffixGenerator.Result result = + pnag.getModifiers(symbols, fingerprint.currencySymbol, properties); + data.setModifiers(result.positive, result.negative, magnitude, plural); + } + } + return data; + } } diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/CompactDecimalFormatTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/CompactDecimalFormatTest.java index 2dca7f2756..7e3f34e813 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/CompactDecimalFormatTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/CompactDecimalFormatTest.java @@ -20,14 +20,18 @@ import java.text.AttributedCharacterIterator; import java.text.CharacterIterator; import java.text.FieldPosition; import java.text.ParsePosition; +import java.util.HashMap; import java.util.Locale; +import java.util.Map; import org.junit.Test; import com.ibm.icu.dev.test.TestFmwk; +import com.ibm.icu.impl.number.Properties; import com.ibm.icu.text.CompactDecimalFormat; import com.ibm.icu.text.CompactDecimalFormat.CompactStyle; import com.ibm.icu.text.DecimalFormat; +import com.ibm.icu.text.DecimalFormat.PropertySetter; import com.ibm.icu.text.NumberFormat; import com.ibm.icu.util.Currency; import com.ibm.icu.util.CurrencyAmount; @@ -649,6 +653,27 @@ public class CompactDecimalFormatTest extends TestFmwk { } } + @Test + public void TestCustomData() { + final Map> customData = new HashMap>(); + Map inner = new HashMap(); + inner.put("one", "0 qwerty"); + inner.put("other", "0 dvorak"); + customData.put("1000", inner); + CompactDecimalFormat cdf = CompactDecimalFormat.getInstance(ULocale.ENGLISH, CompactStyle.SHORT); + cdf.setProperties(new PropertySetter() { + @Override + public void set(Properties props) { + props.setCompactCustomData(customData); + } + }); + assertEquals("Below custom range", "120", cdf.format(123)); + assertEquals("Plural form one", "1 qwerty", cdf.format(1000)); + assertEquals("Plural form other", "1.2 dvorak", cdf.format(1234)); + assertEquals("Above custom range", "12 dvorak", cdf.format(12345)); + assertEquals("Negative number", "-1 qwerty", cdf.format(-1000)); + } + @Test public void TestBug12422() { CompactDecimalFormat cdf; diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/PropertiesTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/PropertiesTest.java index 3c13a77290..cb288d5ce3 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/PropertiesTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/PropertiesTest.java @@ -21,7 +21,9 @@ import java.math.BigDecimal; import java.math.BigInteger; import java.math.MathContext; import java.math.RoundingMode; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; import org.junit.Test; @@ -259,6 +261,20 @@ public class PropertiesTest { FormatWidth[] values = FormatWidth.values(); return values[seed % values.length]; + } else if (type == Map.class) { + // Map> for compactCustomData property + if (seed == 0) return null; + Map> outer = new HashMap>(); + Map inner = new HashMap(); + inner.put("one", "0 thousand"); + StringBuilder magnitudeKey = new StringBuilder(); + magnitudeKey.append("1000"); + for (int i = 0; i < seed % 9; i++) { + magnitudeKey.append("0"); + } + outer.put(magnitudeKey.toString(), inner); + return outer; + } else if (type == MathContext.class) { if (seed == 0) return null; RoundingMode[] modes = RoundingMode.values();