1 // © 2016 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html#License 3 /* 4 ******************************************************************************* 5 * Copyright (C) 2013-2016, International Business Machines Corporation and 6 * others. All Rights Reserved. 7 ******************************************************************************* 8 */ 9 package com.ibm.icu.text; 10 11 import java.text.FieldPosition; 12 13 import com.ibm.icu.impl.SimpleFormatterImpl; 14 import com.ibm.icu.impl.StandardPlural; 15 import com.ibm.icu.text.PluralRules.FixedDecimal; 16 17 /** 18 * QuantityFormatter represents an unknown quantity of something and formats a known quantity 19 * in terms of that something. For example, a QuantityFormatter that represents X apples may 20 * format 1 as "1 apple" and 3 as "3 apples" 21 * <p> 22 * QuanitityFormatter appears here instead of in com.ibm.icu.impl because it depends on 23 * PluralRules and DecimalFormat. It is package-protected as it is not meant for public use. 24 */ 25 class QuantityFormatter { 26 private final SimpleFormatter[] templates = 27 new SimpleFormatter[StandardPlural.COUNT]; 28 QuantityFormatter()29 public QuantityFormatter() {} 30 31 /** 32 * Adds a template if there is none yet for the plural form. 33 * 34 * @param variant the plural variant, e.g "zero", "one", "two", "few", "many", "other" 35 * @param template the text for that plural variant with "{0}" as the quantity. For 36 * example, in English, the template for the "one" variant may be "{0} apple" while the 37 * template for the "other" variant may be "{0} apples" 38 * @throws IllegalArgumentException if variant is not recognized or 39 * if template has more than just the {0} placeholder. 40 */ addIfAbsent(CharSequence variant, String template)41 public void addIfAbsent(CharSequence variant, String template) { 42 int idx = StandardPlural.indexFromString(variant); 43 if (templates[idx] != null) { 44 return; 45 } 46 templates[idx] = SimpleFormatter.compileMinMaxArguments(template, 0, 1); 47 } 48 49 /** 50 * @return true if this object has at least the "other" variant 51 */ isValid()52 public boolean isValid() { 53 return templates[StandardPlural.OTHER_INDEX] != null; 54 } 55 56 /** 57 * Format formats a number with this object. 58 * @param number the number to be formatted 59 * @param numberFormat used to actually format the number. 60 * @param pluralRules uses the number and the numberFormat to determine what plural 61 * variant to use for fetching the formatting template. 62 * @return the formatted string e.g '3 apples' 63 */ format(double number, NumberFormat numberFormat, PluralRules pluralRules)64 public String format(double number, NumberFormat numberFormat, PluralRules pluralRules) { 65 String formatStr = numberFormat.format(number); 66 StandardPlural p = selectPlural(number, numberFormat, pluralRules); 67 SimpleFormatter formatter = templates[p.ordinal()]; 68 if (formatter == null) { 69 formatter = templates[StandardPlural.OTHER_INDEX]; 70 assert formatter != null; 71 } 72 return formatter.format(formatStr); 73 } 74 75 /** 76 * Gets the SimpleFormatter for a particular variant. 77 * @param variant "zero", "one", "two", "few", "many", "other" 78 * @return the SimpleFormatter 79 */ getByVariant(CharSequence variant)80 public SimpleFormatter getByVariant(CharSequence variant) { 81 assert isValid(); 82 int idx = StandardPlural.indexOrOtherIndexFromString(variant); 83 SimpleFormatter template = templates[idx]; 84 return (template == null && idx != StandardPlural.OTHER_INDEX) ? 85 templates[StandardPlural.OTHER_INDEX] : template; 86 } 87 88 // The following methods live here so that class PluralRules does not depend on number formatting, 89 // and the SimpleFormatter does not depend on FieldPosition. 90 91 /** 92 * Selects the standard plural form for the number/formatter/rules. 93 */ selectPlural(double number, NumberFormat numberFormat, PluralRules rules)94 public static StandardPlural selectPlural(double number, NumberFormat numberFormat, PluralRules rules) { 95 String pluralKeyword; 96 if (numberFormat instanceof DecimalFormat) { 97 pluralKeyword = rules.select(((DecimalFormat) numberFormat).getFixedDecimal(number)); 98 } else { 99 pluralKeyword = rules.select(number); 100 } 101 return StandardPlural.orOtherFromString(pluralKeyword); 102 } 103 104 /** 105 * Selects the standard plural form for the number/formatter/rules. 106 */ selectPlural( Number number, NumberFormat fmt, PluralRules rules, StringBuffer formattedNumber, FieldPosition pos)107 public static StandardPlural selectPlural( 108 Number number, NumberFormat fmt, PluralRules rules, 109 StringBuffer formattedNumber, FieldPosition pos) { 110 UFieldPosition fpos = new UFieldPosition(pos.getFieldAttribute(), pos.getField()); 111 fmt.format(number, formattedNumber, fpos); 112 // TODO: Long, BigDecimal & BigInteger may not fit into doubleValue(). 113 FixedDecimal fd = new FixedDecimal( 114 number.doubleValue(), 115 fpos.getCountVisibleFractionDigits(), fpos.getFractionDigits()); 116 String pluralKeyword = rules.select(fd); 117 pos.setBeginIndex(fpos.getBeginIndex()); 118 pos.setEndIndex(fpos.getEndIndex()); 119 return StandardPlural.orOtherFromString(pluralKeyword); 120 } 121 122 /** 123 * Formats the pattern with the value and adjusts the FieldPosition. 124 */ format(String compiledPattern, CharSequence value, StringBuilder appendTo, FieldPosition pos)125 public static StringBuilder format(String compiledPattern, CharSequence value, 126 StringBuilder appendTo, FieldPosition pos) { 127 int[] offsets = new int[1]; 128 SimpleFormatterImpl.formatAndAppend(compiledPattern, appendTo, offsets, value); 129 if (pos.getBeginIndex() != 0 || pos.getEndIndex() != 0) { 130 if (offsets[0] >= 0) { 131 pos.setBeginIndex(pos.getBeginIndex() + offsets[0]); 132 pos.setEndIndex(pos.getEndIndex() + offsets[0]); 133 } else { 134 pos.setBeginIndex(0); 135 pos.setEndIndex(0); 136 } 137 } 138 return appendTo; 139 } 140 } 141