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