26 package sun.util.locale; |
26 package sun.util.locale; |
27 |
27 |
28 import java.util.ArrayList; |
28 import java.util.ArrayList; |
29 import java.util.Collection; |
29 import java.util.Collection; |
30 import java.util.HashMap; |
30 import java.util.HashMap; |
31 import java.util.Iterator; |
|
32 import java.util.LinkedHashMap; |
|
33 import java.util.LinkedList; |
|
34 import java.util.List; |
31 import java.util.List; |
35 import java.util.Locale; |
32 import java.util.Locale; |
36 import java.util.Locale.*; |
33 import java.util.Locale.*; |
37 import static java.util.Locale.FilteringMode.*; |
34 import static java.util.Locale.FilteringMode.*; |
38 import static java.util.Locale.LanguageRange.*; |
35 import static java.util.Locale.LanguageRange.*; |
39 import java.util.Map; |
36 import java.util.Map; |
40 import java.util.Set; |
|
41 |
37 |
42 /** |
38 /** |
43 * Implementation for BCP47 Locale matching |
39 * Implementation for BCP47 Locale matching |
44 * |
40 * |
45 */ |
41 */ |
109 } |
105 } |
110 } |
106 } |
111 |
107 |
112 private static List<String> filterBasic(List<LanguageRange> priorityList, |
108 private static List<String> filterBasic(List<LanguageRange> priorityList, |
113 Collection<String> tags) { |
109 Collection<String> tags) { |
|
110 int splitIndex = splitRanges(priorityList); |
|
111 List<LanguageRange> nonZeroRanges; |
|
112 List<LanguageRange> zeroRanges; |
|
113 if (splitIndex != -1) { |
|
114 nonZeroRanges = priorityList.subList(0, splitIndex); |
|
115 zeroRanges = priorityList.subList(splitIndex, priorityList.size()); |
|
116 } else { |
|
117 nonZeroRanges = priorityList; |
|
118 zeroRanges = List.of(); |
|
119 } |
|
120 |
114 List<String> list = new ArrayList<>(); |
121 List<String> list = new ArrayList<>(); |
115 for (LanguageRange lr : priorityList) { |
122 for (LanguageRange lr : nonZeroRanges) { |
116 String range = lr.getRange(); |
123 String range = lr.getRange(); |
117 if (range.equals("*")) { |
124 if (range.equals("*")) { |
|
125 tags = removeTagsMatchingBasicZeroRange(zeroRanges, tags); |
118 return new ArrayList<String>(tags); |
126 return new ArrayList<String>(tags); |
119 } else { |
127 } else { |
120 for (String tag : tags) { |
128 for (String tag : tags) { |
121 tag = tag.toLowerCase(Locale.ROOT); |
129 tag = tag.toLowerCase(Locale.ROOT); |
122 if (tag.startsWith(range)) { |
130 if (tag.startsWith(range)) { |
123 int len = range.length(); |
131 int len = range.length(); |
124 if ((tag.length() == len || tag.charAt(len) == '-') |
132 if ((tag.length() == len || tag.charAt(len) == '-') |
125 && !list.contains(tag)) { |
133 && !list.contains(tag) |
|
134 && !shouldIgnoreFilterBasicMatch(zeroRanges, tag)) { |
126 list.add(tag); |
135 list.add(tag); |
127 } |
136 } |
128 } |
137 } |
129 } |
138 } |
130 } |
139 } |
131 } |
140 } |
132 |
141 |
133 return list; |
142 return list; |
134 } |
143 } |
135 |
144 |
|
145 /** |
|
146 * Removes the tag(s) which are falling in the basic exclusion range(s) i.e |
|
147 * range(s) with q=0 and returns the updated collection. If the basic |
|
148 * language ranges contains '*' as one of its non zero range then instead of |
|
149 * returning all the tags, remove those which are matching the range with |
|
150 * quality weight q=0. |
|
151 */ |
|
152 private static Collection<String> removeTagsMatchingBasicZeroRange( |
|
153 List<LanguageRange> zeroRange, Collection<String> tags) { |
|
154 if (zeroRange.isEmpty()) { |
|
155 return tags; |
|
156 } |
|
157 |
|
158 List<String> matchingTags = new ArrayList<>(); |
|
159 for (String tag : tags) { |
|
160 tag = tag.toLowerCase(Locale.ROOT); |
|
161 if (!shouldIgnoreFilterBasicMatch(zeroRange, tag)) { |
|
162 matchingTags.add(tag); |
|
163 } |
|
164 } |
|
165 |
|
166 return matchingTags; |
|
167 } |
|
168 |
|
169 /** |
|
170 * The tag which is falling in the basic exclusion range(s) should not |
|
171 * be considered as the matching tag. Ignores the tag matching with the |
|
172 * non-zero ranges, if the tag also matches with one of the basic exclusion |
|
173 * ranges i.e. range(s) having quality weight q=0 |
|
174 */ |
|
175 private static boolean shouldIgnoreFilterBasicMatch( |
|
176 List<LanguageRange> zeroRange, String tag) { |
|
177 if (zeroRange.isEmpty()) { |
|
178 return false; |
|
179 } |
|
180 |
|
181 for (LanguageRange lr : zeroRange) { |
|
182 String range = lr.getRange(); |
|
183 if (range.equals("*")) { |
|
184 return true; |
|
185 } |
|
186 if (tag.startsWith(range)) { |
|
187 int len = range.length(); |
|
188 if ((tag.length() == len || tag.charAt(len) == '-')) { |
|
189 return true; |
|
190 } |
|
191 } |
|
192 } |
|
193 |
|
194 return false; |
|
195 } |
|
196 |
136 private static List<String> filterExtended(List<LanguageRange> priorityList, |
197 private static List<String> filterExtended(List<LanguageRange> priorityList, |
137 Collection<String> tags) { |
198 Collection<String> tags) { |
|
199 int splitIndex = splitRanges(priorityList); |
|
200 List<LanguageRange> nonZeroRanges; |
|
201 List<LanguageRange> zeroRanges; |
|
202 if (splitIndex != -1) { |
|
203 nonZeroRanges = priorityList.subList(0, splitIndex); |
|
204 zeroRanges = priorityList.subList(splitIndex, priorityList.size()); |
|
205 } else { |
|
206 nonZeroRanges = priorityList; |
|
207 zeroRanges = List.of(); |
|
208 } |
|
209 |
138 List<String> list = new ArrayList<>(); |
210 List<String> list = new ArrayList<>(); |
139 for (LanguageRange lr : priorityList) { |
211 for (LanguageRange lr : nonZeroRanges) { |
140 String range = lr.getRange(); |
212 String range = lr.getRange(); |
141 if (range.equals("*")) { |
213 if (range.equals("*")) { |
|
214 tags = removeTagsMatchingExtendedZeroRange(zeroRanges, tags); |
142 return new ArrayList<String>(tags); |
215 return new ArrayList<String>(tags); |
143 } |
216 } |
144 String[] rangeSubtags = range.split("-"); |
217 String[] rangeSubtags = range.split("-"); |
145 for (String tag : tags) { |
218 for (String tag : tags) { |
146 tag = tag.toLowerCase(Locale.ROOT); |
219 tag = tag.toLowerCase(Locale.ROOT); |
148 if (!rangeSubtags[0].equals(tagSubtags[0]) |
221 if (!rangeSubtags[0].equals(tagSubtags[0]) |
149 && !rangeSubtags[0].equals("*")) { |
222 && !rangeSubtags[0].equals("*")) { |
150 continue; |
223 continue; |
151 } |
224 } |
152 |
225 |
153 int rangeIndex = 1; |
226 int rangeIndex = matchFilterExtendedSubtags(rangeSubtags, |
154 int tagIndex = 1; |
227 tagSubtags); |
155 |
228 if (rangeSubtags.length == rangeIndex && !list.contains(tag) |
156 while (rangeIndex < rangeSubtags.length |
229 && !shouldIgnoreFilterExtendedMatch(zeroRanges, tag)) { |
157 && tagIndex < tagSubtags.length) { |
230 list.add(tag); |
158 if (rangeSubtags[rangeIndex].equals("*")) { |
231 } |
159 rangeIndex++; |
|
160 } else if (rangeSubtags[rangeIndex].equals(tagSubtags[tagIndex])) { |
|
161 rangeIndex++; |
|
162 tagIndex++; |
|
163 } else if (tagSubtags[tagIndex].length() == 1 |
|
164 && !tagSubtags[tagIndex].equals("*")) { |
|
165 break; |
|
166 } else { |
|
167 tagIndex++; |
|
168 } |
|
169 } |
|
170 |
|
171 if (rangeSubtags.length == rangeIndex && !list.contains(tag)) { |
|
172 list.add(tag); |
|
173 } |
|
174 } |
232 } |
175 } |
233 } |
176 |
234 |
177 return list; |
235 return list; |
|
236 } |
|
237 |
|
238 /** |
|
239 * Removes the tag(s) which are falling in the extended exclusion range(s) |
|
240 * i.e range(s) with q=0 and returns the updated collection. If the extended |
|
241 * language ranges contains '*' as one of its non zero range then instead of |
|
242 * returning all the tags, remove those which are matching the range with |
|
243 * quality weight q=0. |
|
244 */ |
|
245 private static Collection<String> removeTagsMatchingExtendedZeroRange( |
|
246 List<LanguageRange> zeroRange, Collection<String> tags) { |
|
247 if (zeroRange.isEmpty()) { |
|
248 return tags; |
|
249 } |
|
250 |
|
251 List<String> matchingTags = new ArrayList<>(); |
|
252 for (String tag : tags) { |
|
253 tag = tag.toLowerCase(Locale.ROOT); |
|
254 if (!shouldIgnoreFilterExtendedMatch(zeroRange, tag)) { |
|
255 matchingTags.add(tag); |
|
256 } |
|
257 } |
|
258 |
|
259 return matchingTags; |
|
260 } |
|
261 |
|
262 /** |
|
263 * The tag which is falling in the extended exclusion range(s) should |
|
264 * not be considered as the matching tag. Ignores the tag matching with the |
|
265 * non zero range(s), if the tag also matches with one of the extended |
|
266 * exclusion range(s) i.e. range(s) having quality weight q=0 |
|
267 */ |
|
268 private static boolean shouldIgnoreFilterExtendedMatch( |
|
269 List<LanguageRange> zeroRange, String tag) { |
|
270 if (zeroRange.isEmpty()) { |
|
271 return false; |
|
272 } |
|
273 |
|
274 String[] tagSubtags = tag.split("-"); |
|
275 for (LanguageRange lr : zeroRange) { |
|
276 String range = lr.getRange(); |
|
277 if (range.equals("*")) { |
|
278 return true; |
|
279 } |
|
280 |
|
281 String[] rangeSubtags = range.split("-"); |
|
282 |
|
283 if (!rangeSubtags[0].equals(tagSubtags[0]) |
|
284 && !rangeSubtags[0].equals("*")) { |
|
285 continue; |
|
286 } |
|
287 |
|
288 int rangeIndex = matchFilterExtendedSubtags(rangeSubtags, |
|
289 tagSubtags); |
|
290 if (rangeSubtags.length == rangeIndex) { |
|
291 return true; |
|
292 } |
|
293 } |
|
294 |
|
295 return false; |
|
296 } |
|
297 |
|
298 private static int matchFilterExtendedSubtags(String[] rangeSubtags, |
|
299 String[] tagSubtags) { |
|
300 int rangeIndex = 1; |
|
301 int tagIndex = 1; |
|
302 |
|
303 while (rangeIndex < rangeSubtags.length |
|
304 && tagIndex < tagSubtags.length) { |
|
305 if (rangeSubtags[rangeIndex].equals("*")) { |
|
306 rangeIndex++; |
|
307 } else if (rangeSubtags[rangeIndex] |
|
308 .equals(tagSubtags[tagIndex])) { |
|
309 rangeIndex++; |
|
310 tagIndex++; |
|
311 } else if (tagSubtags[tagIndex].length() == 1 |
|
312 && !tagSubtags[tagIndex].equals("*")) { |
|
313 break; |
|
314 } else { |
|
315 tagIndex++; |
|
316 } |
|
317 } |
|
318 return rangeIndex; |
178 } |
319 } |
179 |
320 |
180 public static Locale lookup(List<LanguageRange> priorityList, |
321 public static Locale lookup(List<LanguageRange> priorityList, |
181 Collection<Locale> locales) { |
322 Collection<Locale> locales) { |
182 if (priorityList.isEmpty() || locales.isEmpty()) { |
323 if (priorityList.isEmpty() || locales.isEmpty()) { |
203 Collection<String> tags) { |
344 Collection<String> tags) { |
204 if (priorityList.isEmpty() || tags.isEmpty()) { |
345 if (priorityList.isEmpty() || tags.isEmpty()) { |
205 return null; |
346 return null; |
206 } |
347 } |
207 |
348 |
208 for (LanguageRange lr : priorityList) { |
349 int splitIndex = splitRanges(priorityList); |
|
350 List<LanguageRange> nonZeroRanges; |
|
351 List<LanguageRange> zeroRanges; |
|
352 if (splitIndex != -1) { |
|
353 nonZeroRanges = priorityList.subList(0, splitIndex); |
|
354 zeroRanges = priorityList.subList(splitIndex, priorityList.size()); |
|
355 } else { |
|
356 nonZeroRanges = priorityList; |
|
357 zeroRanges = List.of(); |
|
358 } |
|
359 |
|
360 for (LanguageRange lr : nonZeroRanges) { |
209 String range = lr.getRange(); |
361 String range = lr.getRange(); |
210 |
362 |
211 // Special language range ("*") is ignored in lookup. |
363 // Special language range ("*") is ignored in lookup. |
212 if (range.equals("*")) { |
364 if (range.equals("*")) { |
213 continue; |
365 continue; |
215 |
367 |
216 String rangeForRegex = range.replaceAll("\\x2A", "\\\\p{Alnum}*"); |
368 String rangeForRegex = range.replaceAll("\\x2A", "\\\\p{Alnum}*"); |
217 while (rangeForRegex.length() > 0) { |
369 while (rangeForRegex.length() > 0) { |
218 for (String tag : tags) { |
370 for (String tag : tags) { |
219 tag = tag.toLowerCase(Locale.ROOT); |
371 tag = tag.toLowerCase(Locale.ROOT); |
220 if (tag.matches(rangeForRegex)) { |
372 if (tag.matches(rangeForRegex) |
|
373 && !shouldIgnoreLookupMatch(zeroRanges, tag)) { |
221 return tag; |
374 return tag; |
222 } |
375 } |
223 } |
376 } |
224 |
377 |
225 // Truncate from the end.... |
378 // Truncate from the end.... |
226 int index = rangeForRegex.lastIndexOf('-'); |
379 rangeForRegex = truncateRange(rangeForRegex); |
227 if (index >= 0) { |
|
228 rangeForRegex = rangeForRegex.substring(0, index); |
|
229 |
|
230 // if range ends with an extension key, truncate it. |
|
231 index = rangeForRegex.lastIndexOf('-'); |
|
232 if (index >= 0 && index == rangeForRegex.length()-2) { |
|
233 rangeForRegex = |
|
234 rangeForRegex.substring(0, rangeForRegex.length()-2); |
|
235 } |
|
236 } else { |
|
237 rangeForRegex = ""; |
|
238 } |
|
239 } |
380 } |
240 } |
381 } |
241 |
382 |
242 return null; |
383 return null; |
|
384 } |
|
385 |
|
386 /** |
|
387 * The tag which is falling in the exclusion range(s) should not be |
|
388 * considered as the matching tag. Ignores the tag matching with the |
|
389 * non zero range(s), if the tag also matches with one of the exclusion |
|
390 * range(s) i.e. range(s) having quality weight q=0. |
|
391 */ |
|
392 private static boolean shouldIgnoreLookupMatch(List<LanguageRange> zeroRange, |
|
393 String tag) { |
|
394 for (LanguageRange lr : zeroRange) { |
|
395 String range = lr.getRange(); |
|
396 |
|
397 // Special language range ("*") is ignored in lookup. |
|
398 if (range.equals("*")) { |
|
399 continue; |
|
400 } |
|
401 |
|
402 String rangeForRegex = range.replaceAll("\\x2A", "\\\\p{Alnum}*"); |
|
403 while (rangeForRegex.length() > 0) { |
|
404 if (tag.matches(rangeForRegex)) { |
|
405 return true; |
|
406 } |
|
407 // Truncate from the end.... |
|
408 rangeForRegex = truncateRange(rangeForRegex); |
|
409 } |
|
410 } |
|
411 |
|
412 return false; |
|
413 } |
|
414 |
|
415 /* Truncate the range from end during the lookup match */ |
|
416 private static String truncateRange(String rangeForRegex) { |
|
417 int index = rangeForRegex.lastIndexOf('-'); |
|
418 if (index >= 0) { |
|
419 rangeForRegex = rangeForRegex.substring(0, index); |
|
420 |
|
421 // if range ends with an extension key, truncate it. |
|
422 index = rangeForRegex.lastIndexOf('-'); |
|
423 if (index >= 0 && index == rangeForRegex.length() - 2) { |
|
424 rangeForRegex |
|
425 = rangeForRegex.substring(0, rangeForRegex.length() - 2); |
|
426 } |
|
427 } else { |
|
428 rangeForRegex = ""; |
|
429 } |
|
430 |
|
431 return rangeForRegex; |
|
432 } |
|
433 |
|
434 /* Returns the split index of the priority list, if it contains |
|
435 * language range(s) with quality weight as 0 i.e. q=0, else -1 |
|
436 */ |
|
437 private static int splitRanges(List<LanguageRange> priorityList) { |
|
438 int size = priorityList.size(); |
|
439 for (int index = 0; index < size; index++) { |
|
440 LanguageRange range = priorityList.get(index); |
|
441 if (range.getWeight() == 0) { |
|
442 return index; |
|
443 } |
|
444 } |
|
445 |
|
446 return -1; // no q=0 range exists |
243 } |
447 } |
244 |
448 |
245 public static List<LanguageRange> parse(String ranges) { |
449 public static List<LanguageRange> parse(String ranges) { |
246 ranges = ranges.replaceAll(" ", "").toLowerCase(Locale.ROOT); |
450 ranges = ranges.replaceAll(" ", "").toLowerCase(Locale.ROOT); |
247 if (ranges.startsWith("accept-language:")) { |
451 if (ranges.startsWith("accept-language:")) { |