• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
3 /*
4  *******************************************************************************
5  * Copyright (C) 2014-2016, International Business Machines Corporation and
6  * others. All Rights Reserved.
7  *******************************************************************************
8  */
9 package com.ibm.icu.impl.locale;
10 
11 import java.util.Collections;
12 import java.util.EnumSet;
13 import java.util.HashMap;
14 import java.util.HashSet;
15 import java.util.LinkedHashMap;
16 import java.util.LinkedHashSet;
17 import java.util.Map;
18 import java.util.MissingResourceException;
19 import java.util.Set;
20 import java.util.regex.Pattern;
21 
22 import com.ibm.icu.impl.ICUData;
23 import com.ibm.icu.impl.ICUResourceBundle;
24 import com.ibm.icu.util.Output;
25 import com.ibm.icu.util.UResourceBundle;
26 import com.ibm.icu.util.UResourceBundleIterator;
27 
28 /**
29  */
30 public class KeyTypeData {
31 
32     public enum ValueType {
33         single, multiple, incremental, any
34     }
35 
36     private static abstract class SpecialTypeHandler {
isWellFormed(String value)37         abstract boolean isWellFormed(String value); // doesn't test validity, just whether it is well formed.
canonicalize(String value)38         String canonicalize(String value) {
39             return AsciiUtil.toLowerString(value);
40         }
41     }
42 
43     private static class CodepointsTypeHandler extends SpecialTypeHandler {
44         private static final Pattern pat = Pattern.compile("[0-9a-fA-F]{4,6}(-[0-9a-fA-F]{4,6})*");
45         @Override
isWellFormed(String value)46         boolean isWellFormed(String value) {
47             return pat.matcher(value).matches();
48         }
49     }
50 
51     private static class ReorderCodeTypeHandler extends SpecialTypeHandler {
52         private static final Pattern pat = Pattern.compile("[a-zA-Z]{3,8}(-[a-zA-Z]{3,8})*");
53         @Override
isWellFormed(String value)54         boolean isWellFormed(String value) {
55             return pat.matcher(value).matches();
56         }
57     }
58 
59     private static class RgKeyValueTypeHandler extends SpecialTypeHandler {
60         private static final Pattern pat = Pattern.compile("([a-zA-Z]{2}|[0-9]{3})[zZ]{4}");
61         @Override
isWellFormed(String value)62         boolean isWellFormed(String value) {
63             return pat.matcher(value).matches();
64         }
65     }
66 
67     private static class ScriptCodeTypeHandler extends SpecialTypeHandler {
68         private static final Pattern pat = Pattern.compile("[a-zA-Z]{4}(-[a-zA-Z]{4})*");
69         @Override
isWellFormed(String value)70         boolean isWellFormed(String value) {
71             return pat.matcher(value).matches();
72         }
73     }
74 
75     private static class SubdivisionKeyValueTypeHandler extends SpecialTypeHandler {
76         private static final Pattern pat = Pattern.compile("([a-zA-Z]{2}|[0-9]{3})");
77         @Override
isWellFormed(String value)78         boolean isWellFormed(String value) {
79             return pat.matcher(value).matches();
80         }
81     }
82 
83     private static class PrivateUseKeyValueTypeHandler extends SpecialTypeHandler {
84         private static final Pattern pat = Pattern.compile("[a-zA-Z0-9]{3,8}(-[a-zA-Z0-9]{3,8})*");
85         @Override
isWellFormed(String value)86         boolean isWellFormed(String value) {
87             return pat.matcher(value).matches();
88         }
89     }
90 
91     private enum SpecialType {
92         CODEPOINTS(new CodepointsTypeHandler()),
93         REORDER_CODE(new ReorderCodeTypeHandler()),
94         RG_KEY_VALUE(new RgKeyValueTypeHandler()),
95         SCRIPT_CODE(new ScriptCodeTypeHandler()),
96         SUBDIVISION_CODE(new SubdivisionKeyValueTypeHandler()),
97         PRIVATE_USE(new PrivateUseKeyValueTypeHandler()),
98         ;
99         SpecialTypeHandler handler;
SpecialType(SpecialTypeHandler handler)100         SpecialType(SpecialTypeHandler handler) {
101             this.handler = handler;
102         }
103     };
104 
105     private static class KeyData {
106         String legacyId;
107         String bcpId;
108         Map<String, Type> typeMap;
109         EnumSet<SpecialType> specialTypes;
110 
KeyData(String legacyId, String bcpId, Map<String, Type> typeMap, EnumSet<SpecialType> specialTypes)111         KeyData(String legacyId, String bcpId, Map<String, Type> typeMap,
112                 EnumSet<SpecialType> specialTypes) {
113             this.legacyId = legacyId;
114             this.bcpId = bcpId;
115             this.typeMap = typeMap;
116             this.specialTypes = specialTypes;
117         }
118     }
119 
120     private static class Type {
121         String legacyId;
122         String bcpId;
123 
Type(String legacyId, String bcpId)124         Type(String legacyId, String bcpId) {
125             this.legacyId = legacyId;
126             this.bcpId = bcpId;
127         }
128     }
129 
toBcpKey(String key)130     public static String toBcpKey(String key) {
131         key = AsciiUtil.toLowerString(key);
132         KeyData keyData = KEYMAP.get(key);
133         if (keyData != null) {
134             return keyData.bcpId;
135         }
136         return null;
137     }
138 
toLegacyKey(String key)139     public static String toLegacyKey(String key) {
140         key = AsciiUtil.toLowerString(key);
141         KeyData keyData = KEYMAP.get(key);
142         if (keyData != null) {
143             return keyData.legacyId;
144         }
145         return null;
146     }
147 
toBcpType(String key, String type, Output<Boolean> isKnownKey, Output<Boolean> isSpecialType)148     public static String toBcpType(String key, String type,
149             Output<Boolean> isKnownKey, Output<Boolean> isSpecialType) {
150 
151         if (isKnownKey != null) {
152             isKnownKey.value = false;
153         }
154         if (isSpecialType != null) {
155             isSpecialType.value = false;
156         }
157 
158         key = AsciiUtil.toLowerString(key);
159         type = AsciiUtil.toLowerString(type);
160 
161         KeyData keyData = KEYMAP.get(key);
162         if (keyData != null) {
163             if (isKnownKey != null) {
164                 isKnownKey.value = Boolean.TRUE;
165             }
166             Type t = keyData.typeMap.get(type);
167             if (t != null) {
168                 return t.bcpId;
169             }
170             if (keyData.specialTypes != null) {
171                 for (SpecialType st : keyData.specialTypes) {
172                     if (st.handler.isWellFormed(type)) {
173                         if (isSpecialType != null) {
174                             isSpecialType.value = true;
175                         }
176                         return st.handler.canonicalize(type);
177                     }
178                 }
179             }
180         }
181         return null;
182     }
183 
184 
toLegacyType(String key, String type, Output<Boolean> isKnownKey, Output<Boolean> isSpecialType)185     public static String toLegacyType(String key, String type,
186             Output<Boolean> isKnownKey, Output<Boolean> isSpecialType) {
187 
188         if (isKnownKey != null) {
189             isKnownKey.value = false;
190         }
191         if (isSpecialType != null) {
192             isSpecialType.value = false;
193         }
194 
195         key = AsciiUtil.toLowerString(key);
196         type = AsciiUtil.toLowerString(type);
197 
198         KeyData keyData = KEYMAP.get(key);
199         if (keyData != null) {
200             if (isKnownKey != null) {
201                 isKnownKey.value = Boolean.TRUE;
202             }
203             Type t = keyData.typeMap.get(type);
204             if (t != null) {
205                 return t.legacyId;
206             }
207             if (keyData.specialTypes != null) {
208                 for (SpecialType st : keyData.specialTypes) {
209                     if (st.handler.isWellFormed(type)) {
210                         if (isSpecialType != null) {
211                             isSpecialType.value = true;
212                         }
213                         return st.handler.canonicalize(type);
214                     }
215                 }
216             }
217         }
218         return null;
219     }
220 
initFromResourceBundle()221     private static void initFromResourceBundle() {
222         UResourceBundle keyTypeDataRes = ICUResourceBundle.getBundleInstance(
223                 ICUData.ICU_BASE_NAME,
224                 "keyTypeData",
225                 ICUResourceBundle.ICU_DATA_CLASS_LOADER,
226                 ICUResourceBundle.OpenType.DIRECT);
227 
228         getKeyInfo(keyTypeDataRes.get("keyInfo"));
229         getTypeInfo(keyTypeDataRes.get("typeInfo"));
230 
231         UResourceBundle keyMapRes = keyTypeDataRes.get("keyMap");
232         UResourceBundle typeMapRes = keyTypeDataRes.get("typeMap");
233 
234         // alias data is optional
235         UResourceBundle typeAliasRes = null;
236         UResourceBundle bcpTypeAliasRes = null;
237 
238         try {
239             typeAliasRes = keyTypeDataRes.get("typeAlias");
240         } catch (MissingResourceException e) {
241             // fall through
242         }
243 
244         try {
245             bcpTypeAliasRes = keyTypeDataRes.get("bcpTypeAlias");
246         } catch (MissingResourceException e) {
247             // fall through
248         }
249 
250         // iterate through keyMap resource
251         UResourceBundleIterator keyMapItr = keyMapRes.getIterator();
252         Map<String,Set<String>> _Bcp47Keys = new LinkedHashMap<String,Set<String>>();
253 
254         while (keyMapItr.hasNext()) {
255             UResourceBundle keyMapEntry = keyMapItr.next();
256             String legacyKeyId = keyMapEntry.getKey();
257             String bcpKeyId = keyMapEntry.getString();
258 
259             boolean hasSameKey = false;
260             if (bcpKeyId.length() == 0) {
261                 // Empty value indicates that BCP key is same with the legacy key.
262                 bcpKeyId = legacyKeyId;
263                 hasSameKey = true;
264             }
265             final LinkedHashSet<String> _bcp47Types = new LinkedHashSet<String>();
266             _Bcp47Keys.put(bcpKeyId, Collections.unmodifiableSet(_bcp47Types));
267 
268             boolean isTZ = legacyKeyId.equals("timezone");
269 
270             // reverse type alias map
271             Map<String, Set<String>> typeAliasMap = null;
272             if (typeAliasRes != null) {
273                 UResourceBundle typeAliasResByKey = null;
274                 try {
275                     typeAliasResByKey = typeAliasRes.get(legacyKeyId);
276                 } catch (MissingResourceException e) {
277                     // fall through
278                 }
279                 if (typeAliasResByKey != null) {
280                     typeAliasMap = new HashMap<String, Set<String>>();
281                     UResourceBundleIterator typeAliasResItr = typeAliasResByKey.getIterator();
282                     while (typeAliasResItr.hasNext()) {
283                         UResourceBundle typeAliasDataEntry = typeAliasResItr.next();
284                         String from = typeAliasDataEntry.getKey();
285                         String to = typeAliasDataEntry.getString();
286                         if (isTZ) {
287                             from = from.replace(':', '/');
288                         }
289                         Set<String> aliasSet = typeAliasMap.get(to);
290                         if (aliasSet == null) {
291                             aliasSet = new HashSet<String>();
292                             typeAliasMap.put(to, aliasSet);
293                         }
294                         aliasSet.add(from);
295                     }
296                 }
297             }
298 
299             // reverse bcp type alias map
300             Map<String, Set<String>> bcpTypeAliasMap = null;
301             if (bcpTypeAliasRes != null) {
302                 UResourceBundle bcpTypeAliasResByKey = null;
303                 try {
304                     bcpTypeAliasResByKey = bcpTypeAliasRes.get(bcpKeyId);
305                 } catch (MissingResourceException e) {
306                     // fall through
307                 }
308                 if (bcpTypeAliasResByKey != null) {
309                     bcpTypeAliasMap = new HashMap<String, Set<String>>();
310                     UResourceBundleIterator bcpTypeAliasResItr = bcpTypeAliasResByKey.getIterator();
311                     while (bcpTypeAliasResItr.hasNext()) {
312                         UResourceBundle bcpTypeAliasDataEntry = bcpTypeAliasResItr.next();
313                         String from = bcpTypeAliasDataEntry.getKey();
314                         String to = bcpTypeAliasDataEntry.getString();
315                         Set<String> aliasSet = bcpTypeAliasMap.get(to);
316                         if (aliasSet == null) {
317                             aliasSet = new HashSet<String>();
318                             bcpTypeAliasMap.put(to, aliasSet);
319                         }
320                         aliasSet.add(from);
321                     }
322                 }
323             }
324 
325             Map<String, Type> typeDataMap = new HashMap<String, Type>();
326             EnumSet<SpecialType> specialTypeSet = null;
327 
328             // look up type map for the key, and walk through the mapping data
329             UResourceBundle typeMapResByKey = null;
330             try {
331                 typeMapResByKey = typeMapRes.get(legacyKeyId);
332             } catch (MissingResourceException e) {
333                 // type map for each key must exist
334                 assert false;
335             }
336             if (typeMapResByKey != null) {
337                 UResourceBundleIterator typeMapResByKeyItr = typeMapResByKey.getIterator();
338                 while (typeMapResByKeyItr.hasNext()) {
339                     UResourceBundle typeMapEntry = typeMapResByKeyItr.next();
340                     String legacyTypeId = typeMapEntry.getKey();
341                     String bcpTypeId = typeMapEntry.getString();
342 
343                     // special types
344                     final char first = legacyTypeId.charAt(0);
345                     final boolean isSpecialType = '9' < first && first < 'a' && bcpTypeId.length() == 0;
346                     if (isSpecialType) {
347                         if (specialTypeSet == null) {
348                             specialTypeSet = EnumSet.noneOf(SpecialType.class);
349                         }
350                         specialTypeSet.add(SpecialType.valueOf(legacyTypeId));
351                         _bcp47Types.add(legacyTypeId);
352                         continue;
353                     }
354 
355                     if (isTZ) {
356                         // a timezone key uses a colon instead of a slash in the resource.
357                         // e.g. America:Los_Angeles
358                         legacyTypeId = legacyTypeId.replace(':', '/');
359                     }
360 
361                     boolean hasSameType = false;
362                     if (bcpTypeId.length() == 0) {
363                         // Empty value indicates that BCP type is same with the legacy type.
364                         bcpTypeId = legacyTypeId;
365                         hasSameType = true;
366                     }
367                     _bcp47Types.add(bcpTypeId);
368 
369                     // Note: legacy type value should never be
370                     // equivalent to bcp type value of a different
371                     // type under the same key. So we use a single
372                     // map for lookup.
373                     Type t = new Type(legacyTypeId, bcpTypeId);
374                     typeDataMap.put(AsciiUtil.toLowerString(legacyTypeId), t);
375                     if (!hasSameType) {
376                         typeDataMap.put(AsciiUtil.toLowerString(bcpTypeId), t);
377                     }
378 
379                     // Also put aliases in the map
380                     if (typeAliasMap != null) {
381                         Set<String> typeAliasSet = typeAliasMap.get(legacyTypeId);
382                         if (typeAliasSet != null) {
383                             for (String alias : typeAliasSet) {
384                                 typeDataMap.put(AsciiUtil.toLowerString(alias), t);
385                             }
386                         }
387                     }
388                     if (bcpTypeAliasMap != null) {
389                         Set<String> bcpTypeAliasSet = bcpTypeAliasMap.get(bcpTypeId);
390                         if (bcpTypeAliasSet != null) {
391                             for (String alias : bcpTypeAliasSet) {
392                                 typeDataMap.put(AsciiUtil.toLowerString(alias), t);
393                             }
394                         }
395                     }
396                 }
397             }
398 
399             KeyData keyData = new KeyData(legacyKeyId, bcpKeyId, typeDataMap, specialTypeSet);
400 
401             KEYMAP.put(AsciiUtil.toLowerString(legacyKeyId), keyData);
402             if (!hasSameKey) {
403                 KEYMAP.put(AsciiUtil.toLowerString(bcpKeyId), keyData);
404             }
405         }
406         BCP47_KEYS = Collections.unmodifiableMap(_Bcp47Keys);
407     }
408 
409     static Set<String> DEPRECATED_KEYS = Collections.emptySet(); // default for no resources
410     static Map<String, ValueType> VALUE_TYPES = Collections.emptyMap(); // default for no resources
411     static Map<String, Set<String>> DEPRECATED_KEY_TYPES = Collections.emptyMap(); // default for no resources
412 
413     private enum KeyInfoType {deprecated, valueType}
414     private enum TypeInfoType {deprecated}
415 
416     /** Reads
417 keyInfo{
418     deprecated{
419         kh{"true"}
420         vt{"true"}
421     }
422     valueType{
423         ca{"incremental"}
424         h0{"single"}
425         kr{"multiple"}
426         vt{"multiple"}
427         x0{"any"}
428     }
429 }
430      */
431     private static void getKeyInfo(UResourceBundle keyInfoRes) {
432         Set<String> _deprecatedKeys = new LinkedHashSet<String>();
433         Map<String, ValueType> _valueTypes = new LinkedHashMap<String, ValueType>();
434         for (UResourceBundleIterator keyInfoIt = keyInfoRes.getIterator(); keyInfoIt.hasNext();) {
435             UResourceBundle keyInfoEntry = keyInfoIt.next();
436             String key = keyInfoEntry.getKey();
437             KeyInfoType keyInfo = KeyInfoType.valueOf(key);
438             for (UResourceBundleIterator keyInfoIt2 = keyInfoEntry.getIterator(); keyInfoIt2.hasNext();) {
439                 UResourceBundle keyInfoEntry2 = keyInfoIt2.next();
440                 String key2 = keyInfoEntry2.getKey();
441                 String value2 = keyInfoEntry2.getString();
442                 switch (keyInfo) {
443                 case deprecated:
444                     _deprecatedKeys.add(key2);
445                     break;
446                 case valueType:
447                     _valueTypes.put(key2, ValueType.valueOf(value2));
448                     break;
449                 }
450             }
451         }
452         DEPRECATED_KEYS = Collections.unmodifiableSet(_deprecatedKeys);
453         VALUE_TYPES = Collections.unmodifiableMap(_valueTypes);
454     }
455 
456     /** Reads:
457 typeInfo{
458     deprecated{
459         co{
460             direct{"true"}
461         }
462         tz{
463             camtr{"true"}
464         }
465     }
466 }
467      */
468     private static void getTypeInfo(UResourceBundle typeInfoRes) {
469         Map<String,Set<String>> _deprecatedKeyTypes = new LinkedHashMap<String,Set<String>>();
470         for (UResourceBundleIterator keyInfoIt = typeInfoRes.getIterator(); keyInfoIt.hasNext();) {
471             UResourceBundle keyInfoEntry = keyInfoIt.next();
472             String key = keyInfoEntry.getKey();
473             TypeInfoType typeInfo = TypeInfoType.valueOf(key);
474             for (UResourceBundleIterator keyInfoIt2 = keyInfoEntry.getIterator(); keyInfoIt2.hasNext();) {
475                 UResourceBundle keyInfoEntry2 = keyInfoIt2.next();
476                 String key2 = keyInfoEntry2.getKey();
477                 Set<String> _deprecatedTypes = new LinkedHashSet<String>();
478                 for (UResourceBundleIterator keyInfoIt3 = keyInfoEntry2.getIterator(); keyInfoIt3.hasNext();) {
479                     UResourceBundle keyInfoEntry3 = keyInfoIt3.next();
480                     String key3 = keyInfoEntry3.getKey();
481                     switch (typeInfo) { // allow for expansion
482                     case deprecated:
483                         _deprecatedTypes.add(key3);
484                         break;
485                     }
486                 }
487                 _deprecatedKeyTypes.put(key2, Collections.unmodifiableSet(_deprecatedTypes));
488             }
489         }
490         DEPRECATED_KEY_TYPES = Collections.unmodifiableMap(_deprecatedKeyTypes);
491     }
492 
493     //
494     // Note:    The key-type data is currently read from ICU resource bundle keyTypeData.res.
495     //          In future, we may import the data into code like below directly from CLDR to
496     //          avoid cyclic dependency between ULocale and UResourceBundle. For now, the code
497     //          below is just for proof of concept, and commented out.
498     //
499 
500     //    private static final String[][] TYPE_DATA_CA = {
501     //     // {<legacy type>, <bcp type - if different>},
502     //        {"buddhist", null},
503     //        {"chinese", null},
504     //        {"coptic", null},
505     //        {"dangi", null},
506     //        {"ethiopic", null},
507     //        {"ethiopic-amete-alem", "ethioaa"},
508     //        {"gregorian", "gregory"},
509     //        {"hebrew", null},
510     //        {"indian", null},
511     //        {"islamic", null},
512     //        {"islamic-civil", null},
513     //        {"islamic-rgsa", null},
514     //        {"islamic-tbla", null},
515     //        {"islamic-umalqura", null},
516     //        {"iso8601", null},
517     //        {"japanese", null},
518     //        {"persian", null},
519     //        {"roc", null},
520     //    };
521     //
522     //    private static final String[][] TYPE_DATA_KS = {
523     //     // {<legacy type>, <bcp type - if different>},
524     //        {"identical", "identic"},
525     //        {"primary", "level1"},
526     //        {"quaternary", "level4"},
527     //        {"secondary", "level2"},
528     //        {"tertiary", "level3"},
529     //    };
530     //
531     //    private static final String[][] TYPE_ALIAS_KS = {
532     //     // {<legacy alias>, <legacy canonical>},
533     //        {"quarternary", "quaternary"},
534     //    };
535     //
536     //    private static final String[][] BCP_TYPE_ALIAS_CA = {
537     //     // {<bcp deprecated>, <bcp preferred>
538     //        {"islamicc", "islamic-civil"},
539     //    };
540     //
541     //    private static final Object[][] KEY_DATA = {
542     //     // {<legacy key>, <bcp key - if different>, <type map>, <type alias>, <bcp type alias>},
543     //        {"calendar", "ca", TYPE_DATA_CA, null, BCP_TYPE_ALIAS_CA},
544     //        {"colstrength", "ks", TYPE_DATA_KS, TYPE_ALIAS_KS, null},
545     //    };
546 
547     private static final Object[][] KEY_DATA = {};
548 
549     @SuppressWarnings("unused")
550     private static void initFromTables() {
551         for (Object[] keyDataEntry : KEY_DATA) {
552             String legacyKeyId = (String)keyDataEntry[0];
553             String bcpKeyId = (String)keyDataEntry[1];
554             String[][] typeData = (String[][])keyDataEntry[2];
555             String[][] typeAliasData = (String[][])keyDataEntry[3];
556             String[][] bcpTypeAliasData = (String[][])keyDataEntry[4];
557 
558             boolean hasSameKey = false;
559             if (bcpKeyId == null) {
560                 bcpKeyId = legacyKeyId;
561                 hasSameKey = true;
562             }
563 
564             // reverse type alias map
565             Map<String, Set<String>> typeAliasMap = null;
566             if (typeAliasData != null) {
567                 typeAliasMap = new HashMap<String, Set<String>>();
568                 for (String[] typeAliasDataEntry : typeAliasData) {
569                     String from = typeAliasDataEntry[0];
570                     String to = typeAliasDataEntry[1];
571                     Set<String> aliasSet = typeAliasMap.get(to);
572                     if (aliasSet == null) {
573                         aliasSet = new HashSet<String>();
574                         typeAliasMap.put(to, aliasSet);
575                     }
576                     aliasSet.add(from);
577                 }
578             }
579 
580             // BCP type alias map data
581             Map<String, Set<String>> bcpTypeAliasMap = null;
582             if (bcpTypeAliasData != null) {
583                 bcpTypeAliasMap = new HashMap<String, Set<String>>();
584                 for (String[] bcpTypeAliasDataEntry : bcpTypeAliasData) {
585                     String from = bcpTypeAliasDataEntry[0];
586                     String to = bcpTypeAliasDataEntry[1];
587                     Set<String> aliasSet = bcpTypeAliasMap.get(to);
588                     if (aliasSet == null) {
589                         aliasSet = new HashSet<String>();
590                         bcpTypeAliasMap.put(to, aliasSet);
591                     }
592                     aliasSet.add(from);
593                 }
594             }
595 
596             // Type map data
597             assert typeData != null;
598             Map<String, Type> typeDataMap = new HashMap<String, Type>();
599             Set<SpecialType> specialTypeSet = null;
600 
601             for (String[] typeDataEntry : typeData) {
602                 String legacyTypeId = typeDataEntry[0];
603                 String bcpTypeId = typeDataEntry[1];
604 
605                 // special types
606                 boolean isSpecialType = false;
607                 for (SpecialType st : SpecialType.values()) {
608                     if (legacyTypeId.equals(st.toString())) {
609                         isSpecialType = true;
610                         if (specialTypeSet == null) {
611                             specialTypeSet = new HashSet<SpecialType>();
612                         }
613                         specialTypeSet.add(st);
614                         break;
615                     }
616                 }
617                 if (isSpecialType) {
618                     continue;
619                 }
620 
621                 boolean hasSameType = false;
622                 if (bcpTypeId == null) {
623                     bcpTypeId = legacyTypeId;
624                     hasSameType = true;
625                 }
626 
627                 // Note: legacy type value should never be
628                 // equivalent to bcp type value of a different
629                 // type under the same key. So we use a single
630                 // map for lookup.
631                 Type t = new Type(legacyTypeId, bcpTypeId);
632                 typeDataMap.put(AsciiUtil.toLowerString(legacyTypeId), t);
633                 if (!hasSameType) {
634                     typeDataMap.put(AsciiUtil.toLowerString(bcpTypeId), t);
635                 }
636 
637                 // Also put aliases in the index
638                 Set<String> typeAliasSet = typeAliasMap.get(legacyTypeId);
639                 if (typeAliasSet != null) {
640                     for (String alias : typeAliasSet) {
641                         typeDataMap.put(AsciiUtil.toLowerString(alias), t);
642                     }
643                 }
644                 Set<String> bcpTypeAliasSet = bcpTypeAliasMap.get(bcpTypeId);
645                 if (bcpTypeAliasSet != null) {
646                     for (String alias : bcpTypeAliasSet) {
647                         typeDataMap.put(AsciiUtil.toLowerString(alias), t);
648                     }
649                 }
650             }
651 
652             EnumSet<SpecialType> specialTypes = null;
653             if (specialTypeSet != null) {
654                 specialTypes = EnumSet.copyOf(specialTypeSet);
655             }
656 
657             KeyData keyData = new KeyData(legacyKeyId, bcpKeyId, typeDataMap, specialTypes);
658 
659             KEYMAP.put(AsciiUtil.toLowerString(legacyKeyId), keyData);
660             if (!hasSameKey) {
661                 KEYMAP.put(AsciiUtil.toLowerString(bcpKeyId), keyData);
662             }
663         }
664     }
665 
666     private static final Map<String, KeyData> KEYMAP = new HashMap<String, KeyData>();
667     private static Map<String, Set<String>> BCP47_KEYS;
668 
669     static {
670         // initFromTables();
671         initFromResourceBundle();
672     }
673 
674     public static Set<String> getBcp47Keys() {
675         return BCP47_KEYS.keySet();
676     };
677 
678     public static Set<String> getBcp47KeyTypes(String key) {
679         return BCP47_KEYS.get(key);
680     };
681 
682     public static boolean isDeprecated(String key) {
683         return DEPRECATED_KEYS.contains(key);
684     }
685 
686     public static boolean isDeprecated(String key, String type) {
687         Set<String> deprecatedTypes = DEPRECATED_KEY_TYPES.get(key);
688         if (deprecatedTypes == null) {
689             return false;
690         }
691         return deprecatedTypes.contains(type);
692     }
693 
694     public static ValueType getValueType(String key) {
695         ValueType type = VALUE_TYPES.get(key);
696         return type == null ? ValueType.single : type;
697     }
698 }
699