1 /* GENERATED SOURCE. DO NOT MODIFY. */ 2 // © 2016 and later: Unicode, Inc. and others. 3 // License & terms of use: http://www.unicode.org/copyright.html#License 4 /* 5 ******************************************************************************* 6 * Copyright (C) 2010-2013, International Business Machines Corporation and * 7 * others. All Rights Reserved. * 8 ******************************************************************************* 9 */ 10 package ohos.global.icu.impl.locale; 11 12 import java.util.ArrayList; 13 import java.util.Collections; 14 import java.util.HashMap; 15 import java.util.List; 16 import java.util.Map; 17 import java.util.Set; 18 19 /** 20 * @hide exposed on OHOS 21 */ 22 public class LanguageTag { 23 private static final boolean JDKIMPL = false; 24 25 // 26 // static fields 27 // 28 public static final String SEP = "-"; 29 public static final String PRIVATEUSE = "x"; 30 public static String UNDETERMINED = "und"; 31 public static final String PRIVUSE_VARIANT_PREFIX = "lvariant"; 32 33 // 34 // Language subtag fields 35 // 36 private String _language = ""; // language subtag 37 private String _script = ""; // script subtag 38 private String _region = ""; // region subtag 39 private String _privateuse = ""; // privateuse 40 41 private List<String> _extlangs = Collections.emptyList(); // extlang subtags 42 private List<String> _variants = Collections.emptyList(); // variant subtags 43 private List<String> _extensions = Collections.emptyList(); // extensions 44 45 // Map contains grandfathered tags and its preferred mappings from 46 // http://www.ietf.org/rfc/rfc5646.txt 47 private static final Map<AsciiUtil.CaseInsensitiveKey, String[]> GRANDFATHERED = 48 new HashMap<AsciiUtil.CaseInsensitiveKey, String[]>(); 49 50 static { 51 // grandfathered = irregular ; non-redundant tags registered 52 // / regular ; during the RFC 3066 era 53 // 54 // irregular = "en-GB-oed" ; irregular tags do not match 55 // / "i-ami" ; the 'langtag' production and 56 // / "i-bnn" ; would not otherwise be 57 // / "i-default" ; considered 'well-formed' 58 // / "i-enochian" ; These tags are all valid, 59 // / "i-hak" ; but most are deprecated 60 // / "i-klingon" ; in favor of more modern 61 // / "i-lux" ; subtags or subtag 62 // / "i-mingo" ; combination 63 // / "i-navajo" 64 // / "i-pwn" 65 // / "i-tao" 66 // / "i-tay" 67 // / "i-tsu" 68 // / "sgn-BE-FR" 69 // / "sgn-BE-NL" 70 // / "sgn-CH-DE" 71 // 72 // regular = "art-lojban" ; these tags match the 'langtag' 73 // / "cel-gaulish" ; production, but their subtags 74 // / "no-bok" ; are not extended language 75 // / "no-nyn" ; or variant subtags: their meaning 76 // / "zh-guoyu" ; is defined by their registration 77 // / "zh-hakka" ; and all of these are deprecated 78 // / "zh-min" ; in favor of a more modern 79 // / "zh-min-nan" ; subtag or sequence of subtags 80 // / "zh-xiang" 81 82 final String[][] entries = { 83 //{"tag", "preferred"}, 84 {"art-lojban", "jbo"}, 85 {"cel-gaulish", "xtg-x-cel-gaulish"}, // fallback 86 {"en-GB-oed", "en-GB-x-oed"}, // fallback 87 {"i-ami", "ami"}, 88 {"i-bnn", "bnn"}, 89 {"i-default", "en-x-i-default"}, // fallback 90 {"i-enochian", "und-x-i-enochian"}, // fallback 91 {"i-hak", "hak"}, 92 {"i-klingon", "tlh"}, 93 {"i-lux", "lb"}, 94 {"i-mingo", "see-x-i-mingo"}, // fallback 95 {"i-navajo", "nv"}, 96 {"i-pwn", "pwn"}, 97 {"i-tao", "tao"}, 98 {"i-tay", "tay"}, 99 {"i-tsu", "tsu"}, 100 {"no-bok", "nb"}, 101 {"no-nyn", "nn"}, 102 {"sgn-BE-FR", "sfb"}, 103 {"sgn-BE-NL", "vgt"}, 104 {"sgn-CH-DE", "sgg"}, 105 {"zh-guoyu", "cmn"}, 106 {"zh-hakka", "hak"}, 107 {"zh-min", "nan-x-zh-min"}, // fallback 108 {"zh-min-nan", "nan"}, 109 {"zh-xiang", "hsn"}, 110 }; 111 for (String[] e : entries) { GRANDFATHERED.put(new AsciiUtil.CaseInsensitiveKey(e[0]), e)112 GRANDFATHERED.put(new AsciiUtil.CaseInsensitiveKey(e[0]), e); 113 } 114 } 115 LanguageTag()116 private LanguageTag() { 117 } 118 119 /* 120 * BNF in RFC5464 121 * 122 * Language-Tag = langtag ; normal language tags 123 * / privateuse ; private use tag 124 * / grandfathered ; grandfathered tags 125 * 126 * 127 * langtag = language 128 * ["-" script] 129 * ["-" region] 130 * *("-" variant) 131 * *("-" extension) 132 * ["-" privateuse] 133 * 134 * language = 2*3ALPHA ; shortest ISO 639 code 135 * ["-" extlang] ; sometimes followed by 136 * ; extended language subtags 137 * / 4ALPHA ; or reserved for future use 138 * / 5*8ALPHA ; or registered language subtag 139 * 140 * extlang = 3ALPHA ; selected ISO 639 codes 141 * *2("-" 3ALPHA) ; permanently reserved 142 * 143 * script = 4ALPHA ; ISO 15924 code 144 * 145 * region = 2ALPHA ; ISO 3166-1 code 146 * / 3DIGIT ; UN M.49 code 147 * 148 * variant = 5*8alphanum ; registered variants 149 * / (DIGIT 3alphanum) 150 * 151 * extension = singleton 1*("-" (2*8alphanum)) 152 * 153 * ; Single alphanumerics 154 * ; "x" reserved for private use 155 * singleton = DIGIT ; 0 - 9 156 * / %x41-57 ; A - W 157 * / %x59-5A ; Y - Z 158 * / %x61-77 ; a - w 159 * / %x79-7A ; y - z 160 * 161 * privateuse = "x" 1*("-" (1*8alphanum)) 162 * 163 */ parse(String languageTag, ParseStatus sts)164 public static LanguageTag parse(String languageTag, ParseStatus sts) { 165 if (sts == null) { 166 sts = new ParseStatus(); 167 } else { 168 sts.reset(); 169 } 170 171 StringTokenIterator itr; 172 boolean isGrandfathered = false; 173 174 // Check if the tag is grandfathered 175 String[] gfmap = GRANDFATHERED.get(new AsciiUtil.CaseInsensitiveKey(languageTag)); 176 // Language tag is at least 2 alpha so we can skip searching the first 2 chars. 177 int dash = 2; 178 while (gfmap == null && (dash = languageTag.indexOf('-', dash + 1)) != -1) { 179 gfmap = GRANDFATHERED.get(new AsciiUtil.CaseInsensitiveKey(languageTag.substring(0, dash))); 180 } 181 182 if (gfmap != null) { 183 if (gfmap[0].length() == languageTag.length()) { 184 // use preferred mapping 185 itr = new StringTokenIterator(gfmap[1], SEP); 186 } else { 187 // append the rest of the tag. 188 itr = new StringTokenIterator(gfmap[1] + languageTag.substring(dash), SEP); 189 } 190 isGrandfathered = true; 191 } else { 192 itr = new StringTokenIterator(languageTag, SEP); 193 } 194 195 LanguageTag tag = new LanguageTag(); 196 197 // langtag must start with either language or privateuse 198 if (tag.parseLanguage(itr, sts)) { 199 // ExtLang can only be preceded by 2-3 letter language subtag. 200 if (tag._language.length() <= 3) 201 tag.parseExtlangs(itr, sts); 202 tag.parseScript(itr, sts); 203 tag.parseRegion(itr, sts); 204 tag.parseVariants(itr, sts); 205 tag.parseExtensions(itr, sts); 206 } 207 tag.parsePrivateuse(itr, sts); 208 209 if (isGrandfathered) { 210 // Grandfathered tag is replaced with a well-formed tag above. 211 // However, the parsed length must be the original tag length. 212 assert (itr.isDone()); 213 assert (!sts.isError()); 214 sts._parseLength = languageTag.length(); 215 } else if (!itr.isDone() && !sts.isError()) { 216 String s = itr.current(); 217 sts._errorIndex = itr.currentStart(); 218 if (s.length() == 0) { 219 sts._errorMsg = "Empty subtag"; 220 } else { 221 sts._errorMsg = "Invalid subtag: " + s; 222 } 223 } 224 225 return tag; 226 } 227 228 // 229 // Language subtag parsers 230 // 231 parseLanguage(StringTokenIterator itr, ParseStatus sts)232 private boolean parseLanguage(StringTokenIterator itr, ParseStatus sts) { 233 if (itr.isDone() || sts.isError()) { 234 return false; 235 } 236 237 boolean found = false; 238 239 String s = itr.current(); 240 if (isLanguage(s)) { 241 found = true; 242 _language = s; 243 sts._parseLength = itr.currentEnd(); 244 itr.next(); 245 } 246 247 return found; 248 } 249 parseExtlangs(StringTokenIterator itr, ParseStatus sts)250 private boolean parseExtlangs(StringTokenIterator itr, ParseStatus sts) { 251 if (itr.isDone() || sts.isError()) { 252 return false; 253 } 254 255 boolean found = false; 256 257 while (!itr.isDone()) { 258 String s = itr.current(); 259 if (!isExtlang(s)) { 260 break; 261 } 262 found = true; 263 if (_extlangs.isEmpty()) { 264 _extlangs = new ArrayList<String>(3); 265 } 266 _extlangs.add(s); 267 sts._parseLength = itr.currentEnd(); 268 itr.next(); 269 270 if (_extlangs.size() == 3) { 271 // Maximum 3 extlangs 272 break; 273 } 274 } 275 276 return found; 277 } 278 parseScript(StringTokenIterator itr, ParseStatus sts)279 private boolean parseScript(StringTokenIterator itr, ParseStatus sts) { 280 if (itr.isDone() || sts.isError()) { 281 return false; 282 } 283 284 boolean found = false; 285 286 String s = itr.current(); 287 if (isScript(s)) { 288 found = true; 289 _script = s; 290 sts._parseLength = itr.currentEnd(); 291 itr.next(); 292 } 293 294 return found; 295 } 296 parseRegion(StringTokenIterator itr, ParseStatus sts)297 private boolean parseRegion(StringTokenIterator itr, ParseStatus sts) { 298 if (itr.isDone() || sts.isError()) { 299 return false; 300 } 301 302 boolean found = false; 303 304 String s = itr.current(); 305 if (isRegion(s)) { 306 found = true; 307 _region = s; 308 sts._parseLength = itr.currentEnd(); 309 itr.next(); 310 } 311 312 return found; 313 } 314 parseVariants(StringTokenIterator itr, ParseStatus sts)315 private boolean parseVariants(StringTokenIterator itr, ParseStatus sts) { 316 if (itr.isDone() || sts.isError()) { 317 return false; 318 } 319 320 boolean found = false; 321 322 while (!itr.isDone()) { 323 String s = itr.current(); 324 if (!isVariant(s)) { 325 break; 326 } 327 found = true; 328 if (_variants.isEmpty()) { 329 _variants = new ArrayList<String>(3); 330 } 331 // Ignore repeated variant 332 s = s.toUpperCase(); 333 if (!_variants.contains(s)) { 334 _variants.add(s); 335 } 336 sts._parseLength = itr.currentEnd(); 337 itr.next(); 338 } 339 340 return found; 341 } 342 parseExtensions(StringTokenIterator itr, ParseStatus sts)343 private boolean parseExtensions(StringTokenIterator itr, ParseStatus sts) { 344 if (itr.isDone() || sts.isError()) { 345 return false; 346 } 347 348 boolean found = false; 349 350 while (!itr.isDone()) { 351 String s = itr.current(); 352 if (isExtensionSingleton(s)) { 353 int start = itr.currentStart(); 354 String singleton = s.toLowerCase(); 355 StringBuilder sb = new StringBuilder(singleton); 356 357 itr.next(); 358 while (!itr.isDone()) { 359 s = itr.current(); 360 if (isExtensionSubtag(s)) { 361 sb.append(SEP).append(s); 362 sts._parseLength = itr.currentEnd(); 363 } else { 364 break; 365 } 366 itr.next(); 367 } 368 369 if (sts._parseLength <= start) { 370 sts._errorIndex = start; 371 sts._errorMsg = "Incomplete extension '" + singleton + "'"; 372 break; 373 } 374 375 if (_extensions.size() == 0) { 376 _extensions = new ArrayList<String>(4); 377 } 378 // Ignore the extension if it is already in _extensions. 379 boolean alreadyHas = false; 380 for (String extension : _extensions) { 381 alreadyHas |= extension.charAt(0) == sb.charAt(0); 382 } 383 if (!alreadyHas) { 384 _extensions.add(sb.toString()); 385 } 386 found = true; 387 } else { 388 break; 389 } 390 } 391 return found; 392 } 393 parsePrivateuse(StringTokenIterator itr, ParseStatus sts)394 private boolean parsePrivateuse(StringTokenIterator itr, ParseStatus sts) { 395 if (itr.isDone() || sts.isError()) { 396 return false; 397 } 398 399 boolean found = false; 400 401 String s = itr.current(); 402 if (isPrivateusePrefix(s)) { 403 int start = itr.currentStart(); 404 StringBuilder sb = new StringBuilder(s); 405 406 itr.next(); 407 while (!itr.isDone()) { 408 s = itr.current(); 409 if (!isPrivateuseSubtag(s)) { 410 break; 411 } 412 sb.append(SEP).append(s); 413 sts._parseLength = itr.currentEnd(); 414 415 itr.next(); 416 } 417 418 if (sts._parseLength <= start) { 419 // need at least 1 private subtag 420 sts._errorIndex = start; 421 sts._errorMsg = "Incomplete privateuse"; 422 } else { 423 _privateuse = sb.toString(); 424 found = true; 425 } 426 } 427 428 return found; 429 } 430 parseLocale(BaseLocale baseLocale, LocaleExtensions localeExtensions)431 public static LanguageTag parseLocale(BaseLocale baseLocale, LocaleExtensions localeExtensions) { 432 LanguageTag tag = new LanguageTag(); 433 434 String language = baseLocale.getLanguage(); 435 String script = baseLocale.getScript(); 436 String region = baseLocale.getRegion(); 437 String variant = baseLocale.getVariant(); 438 439 boolean hasSubtag = false; 440 441 String privuseVar = null; // store ill-formed variant subtags 442 443 if (language.length() > 0 && isLanguage(language)) { 444 // Convert a deprecated language code used by Java to 445 // a new code 446 if (language.equals("iw")) { 447 language = "he"; 448 } else if (language.equals("ji")) { 449 language = "yi"; 450 } else if (language.equals("in")) { 451 language = "id"; 452 } 453 tag._language = language; 454 } 455 456 if (script.length() > 0 && isScript(script)) { 457 tag._script = canonicalizeScript(script); 458 hasSubtag = true; 459 } 460 461 if (region.length() > 0 && isRegion(region)) { 462 tag._region = canonicalizeRegion(region); 463 hasSubtag = true; 464 } 465 466 if (JDKIMPL) { 467 // Special handling for no_NO_NY - use nn_NO for language tag 468 if (tag._language.equals("no") && tag._region.equals("NO") && variant.equals("NY")) { 469 tag._language = "nn"; 470 variant = ""; 471 } 472 } 473 474 if (variant.length() > 0) { 475 List<String> variants = null; 476 StringTokenIterator varitr = new StringTokenIterator(variant, BaseLocale.SEP); 477 while (!varitr.isDone()) { 478 String var = varitr.current(); 479 if (!isVariant(var)) { 480 break; 481 } 482 if (variants == null) { 483 variants = new ArrayList<String>(); 484 } 485 if (JDKIMPL) { 486 variants.add(var); // Do not canonicalize! 487 } else { 488 variants.add(canonicalizeVariant(var)); 489 } 490 varitr.next(); 491 } 492 if (variants != null) { 493 tag._variants = variants; 494 hasSubtag = true; 495 } 496 if (!varitr.isDone()) { 497 // ill-formed variant subtags 498 StringBuilder buf = new StringBuilder(); 499 while (!varitr.isDone()) { 500 String prvv = varitr.current(); 501 if (!isPrivateuseSubtag(prvv)) { 502 // cannot use private use subtag - truncated 503 break; 504 } 505 if (buf.length() > 0) { 506 buf.append(SEP); 507 } 508 if (!JDKIMPL) { 509 prvv = AsciiUtil.toLowerString(prvv); 510 } 511 buf.append(prvv); 512 varitr.next(); 513 } 514 if (buf.length() > 0) { 515 privuseVar = buf.toString(); 516 } 517 } 518 } 519 520 List<String> extensions = null; 521 String privateuse = null; 522 523 Set<Character> locextKeys = localeExtensions.getKeys(); 524 for (Character locextKey : locextKeys) { 525 Extension ext = localeExtensions.getExtension(locextKey); 526 if (isPrivateusePrefixChar(locextKey.charValue())) { 527 privateuse = ext.getValue(); 528 } else { 529 if (extensions == null) { 530 extensions = new ArrayList<String>(); 531 } 532 extensions.add(locextKey.toString() + SEP + ext.getValue()); 533 } 534 } 535 536 if (extensions != null) { 537 tag._extensions = extensions; 538 hasSubtag = true; 539 } 540 541 // append ill-formed variant subtags to private use 542 if (privuseVar != null) { 543 if (privateuse == null) { 544 privateuse = PRIVUSE_VARIANT_PREFIX + SEP + privuseVar; 545 } else { 546 privateuse = privateuse + SEP + PRIVUSE_VARIANT_PREFIX + SEP + privuseVar.replace(BaseLocale.SEP, SEP); 547 } 548 } 549 550 if (privateuse != null) { 551 tag._privateuse = privateuse; 552 } 553 554 if (tag._language.length() == 0 && (hasSubtag || privateuse == null)) { 555 // use lang "und" when 1) no language is available AND 556 // 2) any of other subtags other than private use are available or 557 // no private use tag is available 558 tag._language = UNDETERMINED; 559 } 560 561 return tag; 562 } 563 564 // 565 // Getter methods for language subtag fields 566 // 567 getLanguage()568 public String getLanguage() { 569 return _language; 570 } 571 getExtlangs()572 public List<String> getExtlangs() { 573 return Collections.unmodifiableList(_extlangs); 574 } 575 getScript()576 public String getScript() { 577 return _script; 578 } 579 getRegion()580 public String getRegion() { 581 return _region; 582 } 583 getVariants()584 public List<String> getVariants() { 585 return Collections.unmodifiableList(_variants); 586 } 587 getExtensions()588 public List<String> getExtensions() { 589 return Collections.unmodifiableList(_extensions); 590 } 591 getPrivateuse()592 public String getPrivateuse() { 593 return _privateuse; 594 } 595 596 // 597 // Language subtag syntax checking methods 598 // 599 isLanguage(String s)600 public static boolean isLanguage(String s) { 601 // language = 2*3ALPHA ; shortest ISO 639 code 602 // ["-" extlang] ; sometimes followed by 603 // ; extended language subtags 604 // / 4ALPHA ; or reserved for future use 605 // / 5*8ALPHA ; or registered language subtag 606 return (s.length() >= 2) && (s.length() <= 8) && AsciiUtil.isAlphaString(s); 607 } 608 isExtlang(String s)609 public static boolean isExtlang(String s) { 610 // extlang = 3ALPHA ; selected ISO 639 codes 611 // *2("-" 3ALPHA) ; permanently reserved 612 return (s.length() == 3) && AsciiUtil.isAlphaString(s); 613 } 614 isScript(String s)615 public static boolean isScript(String s) { 616 // script = 4ALPHA ; ISO 15924 code 617 return (s.length() == 4) && AsciiUtil.isAlphaString(s); 618 } 619 isRegion(String s)620 public static boolean isRegion(String s) { 621 // region = 2ALPHA ; ISO 3166-1 code 622 // / 3DIGIT ; UN M.49 code 623 return ((s.length() == 2) && AsciiUtil.isAlphaString(s)) 624 || ((s.length() == 3) && AsciiUtil.isNumericString(s)); 625 } 626 isVariant(String s)627 public static boolean isVariant(String s) { 628 // variant = 5*8alphanum ; registered variants 629 // / (DIGIT 3alphanum) 630 int len = s.length(); 631 if (len >= 5 && len <= 8) { 632 return AsciiUtil.isAlphaNumericString(s); 633 } 634 if (len == 4) { 635 return AsciiUtil.isNumeric(s.charAt(0)) 636 && AsciiUtil.isAlphaNumeric(s.charAt(1)) 637 && AsciiUtil.isAlphaNumeric(s.charAt(2)) 638 && AsciiUtil.isAlphaNumeric(s.charAt(3)); 639 } 640 return false; 641 } 642 isExtensionSingleton(String s)643 public static boolean isExtensionSingleton(String s) { 644 // singleton = DIGIT ; 0 - 9 645 // / %x41-57 ; A - W 646 // / %x59-5A ; Y - Z 647 // / %x61-77 ; a - w 648 // / %x79-7A ; y - z 649 650 return (s.length() == 1) 651 && AsciiUtil.isAlphaNumericString(s) 652 && !AsciiUtil.caseIgnoreMatch(PRIVATEUSE, s); 653 } 654 isExtensionSingletonChar(char c)655 public static boolean isExtensionSingletonChar(char c) { 656 return isExtensionSingleton(String.valueOf(c)); 657 } 658 isExtensionSubtag(String s)659 public static boolean isExtensionSubtag(String s) { 660 // extension = singleton 1*("-" (2*8alphanum)) 661 return (s.length() >= 2) && (s.length() <= 8) && AsciiUtil.isAlphaNumericString(s); 662 } 663 isPrivateusePrefix(String s)664 public static boolean isPrivateusePrefix(String s) { 665 // privateuse = "x" 1*("-" (1*8alphanum)) 666 return (s.length() == 1) 667 && AsciiUtil.caseIgnoreMatch(PRIVATEUSE, s); 668 } 669 isPrivateusePrefixChar(char c)670 public static boolean isPrivateusePrefixChar(char c) { 671 return (AsciiUtil.caseIgnoreMatch(PRIVATEUSE, String.valueOf(c))); 672 } 673 isPrivateuseSubtag(String s)674 public static boolean isPrivateuseSubtag(String s) { 675 // privateuse = "x" 1*("-" (1*8alphanum)) 676 return (s.length() >= 1) && (s.length() <= 8) && AsciiUtil.isAlphaNumericString(s); 677 } 678 679 // 680 // Language subtag canonicalization methods 681 // 682 canonicalizeLanguage(String s)683 public static String canonicalizeLanguage(String s) { 684 return AsciiUtil.toLowerString(s); 685 } 686 canonicalizeExtlang(String s)687 public static String canonicalizeExtlang(String s) { 688 return AsciiUtil.toLowerString(s); 689 } 690 canonicalizeScript(String s)691 public static String canonicalizeScript(String s) { 692 return AsciiUtil.toTitleString(s); 693 } 694 canonicalizeRegion(String s)695 public static String canonicalizeRegion(String s) { 696 return AsciiUtil.toUpperString(s); 697 } 698 canonicalizeVariant(String s)699 public static String canonicalizeVariant(String s) { 700 return AsciiUtil.toLowerString(s); 701 } 702 canonicalizeExtension(String s)703 public static String canonicalizeExtension(String s) { 704 s = AsciiUtil.toLowerString(s); 705 int found; 706 while (s.endsWith("-true")) { 707 s = s.substring(0, s.length() - 5); // length of "-true" is 5 708 } 709 while ((found = s.indexOf("-true-")) > 0) { 710 s = s.substring(0, found) + s.substring(found + 5); // length of "-true" is 5 711 } 712 while (s.endsWith("-yes")) { 713 s = s.substring(0, s.length() - 4); // length of "-yes" is 4 714 } 715 while ((found = s.indexOf("-yes-")) > 0) { 716 s = s.substring(0, found) + s.substring(found + 4); // length of "-yes" is 5 717 } 718 return s; 719 } 720 canonicalizeExtensionSingleton(String s)721 public static String canonicalizeExtensionSingleton(String s) { 722 return AsciiUtil.toLowerString(s); 723 } 724 canonicalizeExtensionSubtag(String s)725 public static String canonicalizeExtensionSubtag(String s) { 726 return AsciiUtil.toLowerString(s); 727 } 728 canonicalizePrivateuse(String s)729 public static String canonicalizePrivateuse(String s) { 730 return AsciiUtil.toLowerString(s); 731 } 732 canonicalizePrivateuseSubtag(String s)733 public static String canonicalizePrivateuseSubtag(String s) { 734 return AsciiUtil.toLowerString(s); 735 } 736 737 @Override toString()738 public String toString() { 739 StringBuilder sb = new StringBuilder(); 740 741 if (_language.length() > 0) { 742 sb.append(_language); 743 744 for (String extlang : _extlangs) { 745 sb.append(SEP).append(extlang); 746 } 747 748 if (_script.length() > 0) { 749 sb.append(SEP).append(_script); 750 } 751 752 if (_region.length() > 0) { 753 sb.append(SEP).append(_region); 754 } 755 756 for (String variant : _variants) { 757 sb.append(SEP).append(variant); 758 } 759 760 for (String extension : _extensions) { 761 sb.append(SEP).append(extension); 762 } 763 } 764 if (_privateuse.length() > 0) { 765 if (sb.length() > 0) { 766 sb.append(SEP); 767 } 768 sb.append(_privateuse); 769 } 770 771 return sb.toString(); 772 } 773 } 774