1/* GENERATED SOURCE. DO NOT MODIFY. */ 2/* 3 ******************************************************************************* 4 * Copyright (C) 2010-2016, Google, Inc.; International Business Machines * 5 * Corporation and others. All Rights Reserved. * 6 ******************************************************************************* 7 */ 8 9package android.icu.util; 10 11import java.util.Collections; 12import java.util.Comparator; 13import java.util.Iterator; 14import java.util.LinkedHashMap; 15import java.util.LinkedHashSet; 16import java.util.Map; 17import java.util.Map.Entry; 18import java.util.Set; 19import java.util.TreeMap; 20import java.util.regex.Matcher; 21import java.util.regex.Pattern; 22 23/** 24 * Provides an immutable list of languages (locales) in priority order. 25 * The string format is based on the Accept-Language format 26 * <a href="http://www.ietf.org/rfc/rfc2616.txt">http://www.ietf.org/rfc/rfc2616.txt</a>, such as 27 * "af, en, fr;q=0.9". Syntactically it is slightly 28 * more lenient, in allowing extra whitespace between elements, extra commas, 29 * and more than 3 decimals (on input), and pins between 0 and 1. 30 * <p>In theory, Accept-Language indicates the relative 'quality' of each item, 31 * but in practice, all of the browsers just take an ordered list, like 32 * "en, fr, de", and synthesize arbitrary quality values that put these in the 33 * right order, like: "en, fr;q=0.7, de;q=0.3". The quality values in these de facto 34 * semantics thus have <b>nothing</b> to do with the relative qualities of the 35 * original. Accept-Language also doesn't 36 * specify the interpretation of multiple instances, eg what "en, fr, en;q=.5" 37 * means. 38 * <p>There are various ways to build a LanguagePriorityList, such 39 * as using the following equivalent patterns: 40 * 41 * <pre> 42 * list = LanguagePriorityList.add("af, en, fr;q=0.9").build(); 43 * 44 * list2 = LanguagePriorityList 45 * .add(ULocale.forString("af")) 46 * .add(ULocale.ENGLISH) 47 * .add(ULocale.FRENCH, 0.9d) 48 * .build(); 49 * </pre> 50 * When the list is built, the internal values are sorted in descending order by 51 * weight, and then by input order. That is, if two languages have the same weight, the first one in the original order 52 * comes first. If exactly the same language tag appears multiple times, 53 * the last one wins. 54 * 55 * There are two options when building. If preserveWeights are on, then "de;q=0.3, ja;q=0.3, en, fr;q=0.7, de " would result in the following: 56 * <pre> en;q=1.0 57 * de;q=1.0 58 * fr;q=0.7 59 * ja;q=0.3</pre> 60 * If it is off (the default), then all weights are reset to 1.0 after reordering. 61 * This is to match the effect of the Accept-Language semantics as used in browsers, and results in the following: 62 * * <pre> en;q=1.0 63 * de;q=1.0 64 * fr;q=1.0 65 * ja;q=1.0</pre> 66 * @author markdavis@google.com 67 * @hide Only a subset of ICU is exposed in Android 68 */ 69public class LocalePriorityList implements Iterable<ULocale> { 70 private static final double D0 = 0.0d; 71 private static final Double D1 = 1.0d; 72 73 private static final Pattern languageSplitter = Pattern.compile("\\s*,\\s*"); 74 private static final Pattern weightSplitter = Pattern 75 .compile("\\s*(\\S*)\\s*;\\s*q\\s*=\\s*(\\S*)"); 76 private final Map<ULocale, Double> languagesAndWeights; 77 78 /** 79 * Add a language code to the list being built, with weight 1.0. 80 * 81 * @param languageCode locale/language to be added 82 * @return internal builder, for chaining 83 */ 84 public static Builder add(ULocale... languageCode) { 85 return new Builder().add(languageCode); 86 } 87 88 /** 89 * Add a language code to the list being built, with specified weight. 90 * 91 * @param languageCode locale/language to be added 92 * @param weight value from 0.0 to 1.0 93 * @return internal builder, for chaining 94 */ 95 public static Builder add(ULocale languageCode, final double weight) { 96 return new Builder().add(languageCode, weight); 97 } 98 99 /** 100 * Add a language priority list. 101 * 102 * @param languagePriorityList list to add all the members of 103 * @return internal builder, for chaining 104 */ 105 public static Builder add(LocalePriorityList languagePriorityList) { 106 return new Builder().add(languagePriorityList); 107 } 108 109 /** 110 * Add language codes to the list being built, using a string in rfc2616 111 * (lenient) format, where each language is a valid {@link ULocale}. 112 * 113 * @param acceptLanguageString String in rfc2616 format (but leniently parsed) 114 * @return internal builder, for chaining 115 */ 116 public static Builder add(String acceptLanguageString) { 117 return new Builder().add(acceptLanguageString); 118 } 119 120 /** 121 * Return the weight for a given language, or null if there is none. Note that 122 * the weights may be adjusted from those used to build the list. 123 * 124 * @param language to get weight of 125 * @return weight 126 */ 127 public Double getWeight(ULocale language) { 128 return languagesAndWeights.get(language); 129 } 130 131 /** 132 * {@inheritDoc} 133 */ 134 @Override 135 public String toString() { 136 final StringBuilder result = new StringBuilder(); 137 for (final ULocale language : languagesAndWeights.keySet()) { 138 if (result.length() != 0) { 139 result.append(", "); 140 } 141 result.append(language); 142 double weight = languagesAndWeights.get(language); 143 if (weight != D1) { 144 result.append(";q=").append(weight); 145 } 146 } 147 return result.toString(); 148 } 149 150 /** 151 * {@inheritDoc} 152 */ 153 public Iterator<ULocale> iterator() { 154 return languagesAndWeights.keySet().iterator(); 155 } 156 157 /** 158 * {@inheritDoc} 159 */ 160 @Override 161 public boolean equals(final Object o) { 162 if (o == null) { 163 return false; 164 } 165 if (this == o) { 166 return true; 167 } 168 try { 169 final LocalePriorityList that = (LocalePriorityList) o; 170 return languagesAndWeights.equals(that.languagesAndWeights); 171 } catch (final RuntimeException e) { 172 return false; 173 } 174 } 175 176 /** 177 * {@inheritDoc} 178 */ 179 @Override 180 public int hashCode() { 181 return languagesAndWeights.hashCode(); 182 } 183 184 // ==================== Privates ==================== 185 186 187 private LocalePriorityList(final Map<ULocale, Double> languageToWeight) { 188 this.languagesAndWeights = languageToWeight; 189 } 190 191 /** 192 * Class used for building LanguagePriorityLists 193 */ 194 public static class Builder { 195 /** 196 * These store the input languages and weights, in chronological order, 197 * where later additions override previous ones. 198 */ 199 private final Map<ULocale, Double> languageToWeight 200 = new LinkedHashMap<ULocale, Double>(); 201 202 /* 203 * Private constructor, only used by LocalePriorityList 204 */ 205 private Builder() { 206 } 207 208 /** 209 * Creates a LocalePriorityList. This is equivalent to 210 * {@link Builder#build(boolean) Builder.build(false)}. 211 * 212 * @return A LocalePriorityList 213 */ 214 public LocalePriorityList build() { 215 return build(false); 216 } 217 218 /** 219 * Creates a LocalePriorityList. 220 * 221 * @param preserveWeights when true, the weights originally came 222 * from a language priority list specified by add() are preserved. 223 * @return A LocalePriorityList 224 */ 225 public LocalePriorityList build(boolean preserveWeights) { 226 // Walk through the input list, collecting the items with the same weights. 227 final Map<Double, Set<ULocale>> doubleCheck = new TreeMap<Double, Set<ULocale>>( 228 myDescendingDouble); 229 for (final ULocale lang : languageToWeight.keySet()) { 230 Double weight = languageToWeight.get(lang); 231 Set<ULocale> s = doubleCheck.get(weight); 232 if (s == null) { 233 doubleCheck.put(weight, s = new LinkedHashSet<ULocale>()); 234 } 235 s.add(lang); 236 } 237 // We now have a bunch of items sorted by weight, then chronologically. 238 // We can now create a list in the right order 239 final Map<ULocale, Double> temp = new LinkedHashMap<ULocale, Double>(); 240 for (Entry<Double, Set<ULocale>> langEntry : doubleCheck.entrySet()) { 241 final Double weight = langEntry.getKey(); 242 for (final ULocale lang : langEntry.getValue()) { 243 temp.put(lang, preserveWeights ? weight : D1); 244 } 245 } 246 return new LocalePriorityList(Collections.unmodifiableMap(temp)); 247 } 248 249 /** 250 * Adds a LocalePriorityList 251 * 252 * @param languagePriorityList a LocalePriorityList 253 * @return this, for chaining 254 */ 255 public Builder add( 256 final LocalePriorityList languagePriorityList) { 257 for (final ULocale language : languagePriorityList.languagesAndWeights 258 .keySet()) { 259 add(language, languagePriorityList.languagesAndWeights.get(language)); 260 } 261 return this; 262 } 263 264 /** 265 * Adds a new language code, with weight = 1.0. 266 * 267 * @param languageCode to add with weight 1.0 268 * @return this, for chaining 269 */ 270 public Builder add(final ULocale languageCode) { 271 return add(languageCode, D1); 272 } 273 274 /** 275 * Adds language codes, with each having weight = 1.0. 276 * 277 * @param languageCodes List of language codes. 278 * @return this, for chaining. 279 */ 280 public Builder add(ULocale... languageCodes) { 281 for (final ULocale languageCode : languageCodes) { 282 add(languageCode, D1); 283 } 284 return this; 285 } 286 287 /** 288 * Adds a new supported languageCode, with specified weight. Overrides any 289 * previous weight for the language. 290 * 291 * @param languageCode language/locale to add 292 * @param weight value between 0.0 and 1.1 293 * @return this, for chaining. 294 */ 295 public Builder add(final ULocale languageCode, 296 double weight) { 297 if (languageToWeight.containsKey(languageCode)) { 298 languageToWeight.remove(languageCode); 299 } 300 if (weight <= D0) { 301 return this; // skip zeros 302 } else if (weight > D1) { 303 weight = D1; 304 } 305 languageToWeight.put(languageCode, weight); 306 return this; 307 } 308 309 /** 310 * Adds rfc2616 list. 311 * 312 * @param acceptLanguageList in rfc2616 format 313 * @return this, for chaining. 314 */ 315 public Builder add(final String acceptLanguageList) { 316 final String[] items = languageSplitter.split(acceptLanguageList.trim()); 317 final Matcher itemMatcher = weightSplitter.matcher(""); 318 for (final String item : items) { 319 if (itemMatcher.reset(item).matches()) { 320 final ULocale language = new ULocale(itemMatcher.group(1)); 321 final double weight = Double.parseDouble(itemMatcher.group(2)); 322 if (!(weight >= D0 && weight <= D1)) { // do ! for NaN 323 throw new IllegalArgumentException("Illegal weight, must be 0..1: " 324 + weight); 325 } 326 add(language, weight); 327 } else if (item.length() != 0) { 328 add(new ULocale(item)); 329 } 330 } 331 return this; 332 } 333 } 334 335 private static Comparator<Double> myDescendingDouble = new Comparator<Double>() { 336 public int compare(Double o1, Double o2) { 337 return -o1.compareTo(o2); 338 } 339 }; 340} 341