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