jdk/src/java.base/share/classes/sun/util/locale/LocaleMatcher.java
changeset 40114 0194b0ba95c6
parent 39053 668c400a8dd1
child 41955 6a6fd4af5236
equal deleted inserted replaced
40113:7ba318a6b751 40114:0194b0ba95c6
    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:")) {