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