1 /* GENERATED SOURCE. DO NOT MODIFY. */ 2 // 2016 and later: Unicode, Inc. and others. 3 // License & terms of use: http://www.unicode.org/copyright.html#License 4 /* 5 ******************************************************************************* 6 * Copyright (C) 2013-2015, International Business Machines Corporation and 7 * others. All Rights Reserved. 8 ******************************************************************************* 9 */ 10 package android.icu.text; 11 12 import java.util.ArrayList; 13 import java.util.Collection; 14 import java.util.Collections; 15 import java.util.HashMap; 16 import java.util.HashSet; 17 import java.util.LinkedHashSet; 18 import java.util.List; 19 import java.util.Map; 20 import java.util.Map.Entry; 21 import java.util.Set; 22 import java.util.TreeSet; 23 24 import android.icu.text.PluralRules.FixedDecimal; 25 import android.icu.text.PluralRules.KeywordStatus; 26 import android.icu.util.Output; 27 28 /** 29 * @author markdavis 30 * Refactor samples as first step to moving into CLDR 31 * 32 * @deprecated This API is ICU internal only. 33 * @hide Only a subset of ICU is exposed in Android 34 * @hide draft / provisional / internal are hidden on Android 35 */ 36 @Deprecated 37 public class PluralSamples { 38 39 private PluralRules pluralRules; 40 private final Map<String, List<Double>> _keySamplesMap; 41 42 /** 43 * @deprecated This API is ICU internal only. 44 * @hide draft / provisional / internal are hidden on Android 45 */ 46 @Deprecated 47 public final Map<String, Boolean> _keyLimitedMap; 48 private final Map<String, Set<FixedDecimal>> _keyFractionSamplesMap; 49 private final Set<FixedDecimal> _fractionSamples; 50 51 /** 52 * @deprecated This API is ICU internal only. 53 * @hide draft / provisional / internal are hidden on Android 54 */ 55 @Deprecated 56 public PluralSamples(PluralRules pluralRules) { 57 this.pluralRules = pluralRules; 58 Set<String> keywords = pluralRules.getKeywords(); 59 // ensure both _keySamplesMap and _keyLimitedMap are initialized. 60 // If this were allowed to vary on a per-call basis, we'd have to recheck and 61 // possibly rebuild the samples cache. Doesn't seem worth it. 62 // This 'max samples' value only applies to keywords that are unlimited, for 63 // other keywords all the matching values are returned. This might be a lot. 64 final int MAX_SAMPLES = 3; 65 66 Map<String, Boolean> temp = new HashMap<String, Boolean>(); 67 for (String k : keywords) { 68 temp.put(k, pluralRules.isLimited(k)); 69 } 70 _keyLimitedMap = temp; 71 72 Map<String, List<Double>> sampleMap = new HashMap<String, List<Double>>(); 73 int keywordsRemaining = keywords.size(); 74 75 int limit = 128; // Math.max(5, getRepeatLimit() * MAX_SAMPLES) * 2; 76 77 for (int i = 0; keywordsRemaining > 0 && i < limit; ++i) { 78 keywordsRemaining = addSimpleSamples(pluralRules, MAX_SAMPLES, sampleMap, keywordsRemaining, i / 2.0); 79 } 80 // Hack for Celtic 81 keywordsRemaining = addSimpleSamples(pluralRules, MAX_SAMPLES, sampleMap, keywordsRemaining, 1000000); 82 83 84 // collect explicit samples 85 Map<String, Set<FixedDecimal>> sampleFractionMap = new HashMap<String, Set<FixedDecimal>>(); 86 Set<FixedDecimal> mentioned = new TreeSet<FixedDecimal>(); 87 // make sure that there is at least one 'other' value 88 Map<String, Set<FixedDecimal>> foundKeywords = new HashMap<String, Set<FixedDecimal>>(); 89 for (FixedDecimal s : mentioned) { 90 String keyword = pluralRules.select(s); 91 addRelation(foundKeywords, keyword, s); 92 } 93 main: 94 if (foundKeywords.size() != keywords.size()) { 95 for (int i = 1; i < 1000; ++i) { 96 boolean done = addIfNotPresent(i, mentioned, foundKeywords); 97 if (done) break main; 98 } 99 // if we are not done, try tenths 100 for (int i = 10; i < 1000; ++i) { 101 boolean done = addIfNotPresent(i/10d, mentioned, foundKeywords); 102 if (done) break main; 103 } 104 System.out.println("Failed to find sample for each keyword: " + foundKeywords + "\n\t" + pluralRules + "\n\t" + mentioned); 105 } 106 mentioned.add(new FixedDecimal(0)); // always there 107 mentioned.add(new FixedDecimal(1)); // always there 108 mentioned.add(new FixedDecimal(2)); // always there 109 mentioned.add(new FixedDecimal(0.1,1)); // always there 110 mentioned.add(new FixedDecimal(1.99,2)); // always there 111 mentioned.addAll(fractions(mentioned)); 112 for (FixedDecimal s : mentioned) { 113 String keyword = pluralRules.select(s); 114 Set<FixedDecimal> list = sampleFractionMap.get(keyword); 115 if (list == null) { 116 list = new LinkedHashSet<FixedDecimal>(); // will be sorted because the iteration is 117 sampleFractionMap.put(keyword, list); 118 } 119 list.add(s); 120 } 121 122 if (keywordsRemaining > 0) { 123 for (String k : keywords) { 124 if (!sampleMap.containsKey(k)) { 125 sampleMap.put(k, Collections.<Double>emptyList()); 126 } 127 if (!sampleFractionMap.containsKey(k)) { 128 sampleFractionMap.put(k, Collections.<FixedDecimal>emptySet()); 129 } 130 } 131 } 132 133 // Make lists immutable so we can return them directly 134 for (Entry<String, List<Double>> entry : sampleMap.entrySet()) { 135 sampleMap.put(entry.getKey(), Collections.unmodifiableList(entry.getValue())); 136 } 137 for (Entry<String, Set<FixedDecimal>> entry : sampleFractionMap.entrySet()) { 138 sampleFractionMap.put(entry.getKey(), Collections.unmodifiableSet(entry.getValue())); 139 } 140 _keySamplesMap = sampleMap; 141 _keyFractionSamplesMap = sampleFractionMap; 142 _fractionSamples = Collections.unmodifiableSet(mentioned); 143 } 144 145 private int addSimpleSamples(PluralRules pluralRules, final int MAX_SAMPLES, Map<String, List<Double>> sampleMap, 146 int keywordsRemaining, double val) { 147 String keyword = pluralRules.select(val); 148 boolean keyIsLimited = _keyLimitedMap.get(keyword); 149 150 List<Double> list = sampleMap.get(keyword); 151 if (list == null) { 152 list = new ArrayList<Double>(MAX_SAMPLES); 153 sampleMap.put(keyword, list); 154 } else if (!keyIsLimited && list.size() == MAX_SAMPLES) { 155 return keywordsRemaining; 156 } 157 list.add(Double.valueOf(val)); 158 159 if (!keyIsLimited && list.size() == MAX_SAMPLES) { 160 --keywordsRemaining; 161 } 162 return keywordsRemaining; 163 } 164 165 private void addRelation(Map<String, Set<FixedDecimal>> foundKeywords, String keyword, FixedDecimal s) { 166 Set<FixedDecimal> set = foundKeywords.get(keyword); 167 if (set == null) { 168 foundKeywords.put(keyword, set = new HashSet<FixedDecimal>()); 169 } 170 set.add(s); 171 } 172 173 private boolean addIfNotPresent(double d, Set<FixedDecimal> mentioned, Map<String, Set<FixedDecimal>> foundKeywords) { 174 FixedDecimal numberInfo = new FixedDecimal(d); 175 String keyword = pluralRules.select(numberInfo); 176 if (!foundKeywords.containsKey(keyword) || keyword.equals("other")) { 177 addRelation(foundKeywords, keyword, numberInfo); 178 mentioned.add(numberInfo); 179 if (keyword.equals("other")) { 180 if (foundKeywords.get("other").size() > 1) { 181 return true; 182 } 183 } 184 } 185 return false; 186 } 187 188 private static final int[] TENS = {1, 10, 100, 1000, 10000, 100000, 1000000}; 189 190 private static final int LIMIT_FRACTION_SAMPLES = 3; 191 192 193 private Set<FixedDecimal> fractions(Set<FixedDecimal> original) { 194 Set<FixedDecimal> toAddTo = new HashSet<FixedDecimal>(); 195 196 Set<Integer> result = new HashSet<Integer>(); 197 for (FixedDecimal base1 : original) { 198 result.add((int)base1.integerValue); 199 } 200 List<Integer> ints = new ArrayList<Integer>(result); 201 Set<String> keywords = new HashSet<String>(); 202 203 for (int j = 0; j < ints.size(); ++j) { 204 Integer base = ints.get(j); 205 String keyword = pluralRules.select(base); 206 if (keywords.contains(keyword)) { 207 continue; 208 } 209 keywords.add(keyword); 210 toAddTo.add(new FixedDecimal(base,1)); // add .0 211 toAddTo.add(new FixedDecimal(base,2)); // add .00 212 Integer fract = getDifferentCategory(ints, keyword); 213 if (fract >= TENS[LIMIT_FRACTION_SAMPLES-1]) { // make sure that we always get the value 214 toAddTo.add(new FixedDecimal(base + "." + fract)); 215 } else { 216 for (int visibleFractions = 1; visibleFractions < LIMIT_FRACTION_SAMPLES; ++visibleFractions) { 217 for (int i = 1; i <= visibleFractions; ++i) { 218 // with visible fractions = 3, and fract = 1, then we should get x.10, 0.01 219 // with visible fractions = 3, and fract = 15, then we should get x.15, x.15 220 if (fract >= TENS[i]) { 221 continue; 222 } 223 toAddTo.add(new FixedDecimal(base + fract/(double)TENS[i], visibleFractions)); 224 } 225 } 226 } 227 } 228 return toAddTo; 229 } 230 231 private Integer getDifferentCategory(List<Integer> ints, String keyword) { 232 for (int i = ints.size() - 1; i >= 0; --i) { 233 Integer other = ints.get(i); 234 String keywordOther = pluralRules.select(other); 235 if (!keywordOther.equals(keyword)) { 236 return other; 237 } 238 } 239 return 37; 240 } 241 242 /** 243 * @deprecated This API is ICU internal only. 244 * @hide draft / provisional / internal are hidden on Android 245 */ 246 @Deprecated 247 public KeywordStatus getStatus(String keyword, int offset, Set<Double> explicits, Output<Double> uniqueValue) { 248 if (uniqueValue != null) { 249 uniqueValue.value = null; 250 } 251 252 if (!pluralRules.getKeywords().contains(keyword)) { 253 return KeywordStatus.INVALID; 254 } 255 Collection<Double> values = pluralRules.getAllKeywordValues(keyword); 256 if (values == null) { 257 return KeywordStatus.UNBOUNDED; 258 } 259 int originalSize = values.size(); 260 261 if (explicits == null) { 262 explicits = Collections.emptySet(); 263 } 264 265 // Quick check on whether there are multiple elements 266 267 if (originalSize > explicits.size()) { 268 if (originalSize == 1) { 269 if (uniqueValue != null) { 270 uniqueValue.value = values.iterator().next(); 271 } 272 return KeywordStatus.UNIQUE; 273 } 274 return KeywordStatus.BOUNDED; 275 } 276 277 // Compute if the quick test is insufficient. 278 279 HashSet<Double> subtractedSet = new HashSet<Double>(values); 280 for (Double explicit : explicits) { 281 subtractedSet.remove(explicit - offset); 282 } 283 if (subtractedSet.size() == 0) { 284 return KeywordStatus.SUPPRESSED; 285 } 286 287 if (uniqueValue != null && subtractedSet.size() == 1) { 288 uniqueValue.value = subtractedSet.iterator().next(); 289 } 290 291 return originalSize == 1 ? KeywordStatus.UNIQUE : KeywordStatus.BOUNDED; 292 } 293 294 Map<String, List<Double>> getKeySamplesMap() { 295 return _keySamplesMap; 296 } 297 298 Map<String, Set<FixedDecimal>> getKeyFractionSamplesMap() { 299 return _keyFractionSamplesMap; 300 } 301 302 Set<FixedDecimal> getFractionSamples() { 303 return _fractionSamples; 304 } 305 306 /** 307 * Returns all the values that trigger this keyword, or null if the number of such 308 * values is unlimited. 309 * 310 * @param keyword the keyword 311 * @return the values that trigger this keyword, or null. The returned collection 312 * is immutable. It will be empty if the keyword is not defined. 313 */ 314 315 Collection<Double> getAllKeywordValues(String keyword) { 316 // HACK for now 317 if (!pluralRules.getKeywords().contains(keyword)) { 318 return Collections.<Double>emptyList(); 319 } 320 Collection<Double> result = getKeySamplesMap().get(keyword); 321 322 // We depend on MAX_SAMPLES here. It's possible for a conjunction 323 // of unlimited rules that 'looks' unlimited to return a limited 324 // number of values. There's no bounds to this limited number, in 325 // general, because you can construct arbitrarily complex rules. Since 326 // we always generate 3 samples if a rule is really unlimited, that's 327 // where we put the cutoff. 328 if (result.size() > 2 && !_keyLimitedMap.get(keyword)) { 329 return null; 330 } 331 return result; 332 } 333 } 334