8159337: Introduce a method in Locale class to return the language tags as per RFC 5646 convention

Reviewed-by: naoto, rriggs
This commit is contained in:
Justin Lu 2023-05-09 17:15:06 +00:00
parent 3aff5eacbd
commit 82bcee76ea
3 changed files with 276 additions and 5 deletions

View file

@ -1689,6 +1689,58 @@ public final class Locale implements Cloneable, Serializable {
return langTag;
}
/**
* {@return a case folded IETF BCP 47 language tag}
*
* <p>This method formats a language tag into one with case convention
* that adheres to section 2.1.1. Formatting of Language Tags of RFC5646.
* This format is defined as: <i>All subtags, including extension and private
* use subtags, use lowercase letters with two exceptions: two-letter
* and four-letter subtags that neither appear at the start of the tag
* nor occur after singletons. Such two-letter subtags are all
* uppercase (as in the tags "en-CA-x-ca" or "sgn-BE-FR") and four-
* letter subtags are titlecase (as in the tag "az-Latn-x-latn").</i> As
* legacy tags, (defined as "grandfathered" in RFC5646) are not always well-formed, this method
* will simply case fold a legacy tag to match the exact case convention
* for the particular tag specified in the respective
* {@link ##legacy_tags Legacy tags} table.
*
* <p><b>Special Exceptions</b>
* <p>To maintain consistency with {@link ##def_variant variant}
* which is case-sensitive, this method will neither case fold variant
* subtags nor case fold private use subtags prefixed by {@code lvariant}.
*
* <p>For example,
* {@snippet lang=java :
* String tag = "ja-kana-jp-x-lvariant-Oracle-JDK-Standard-Edition";
* Locale.caseFoldLanguageTag(tag); // returns "ja-Kana-JP-x-lvariant-Oracle-JDK-Standard-Edition"
* String tag2 = "ja-kana-jp-x-Oracle-JDK-Standard-Edition";
* Locale.caseFoldLanguageTag(tag2); // returns "ja-Kana-JP-x-oracle-jdk-standard-edition"
* }
*
* <p>Excluding case folding, this method makes no modifications to the tag itself.
* Case convention of language tags does not carry meaning, and is simply
* recommended as it corresponds with various ISO standards, including:
* ISO639-1, ISO15924, and ISO3166-1.
*
* <p>As the formatting of the case convention is dependent on the
* positioning of certain subtags, callers of this method should ensure
* that the language tag is well-formed, (conforming to section 2.1. Syntax
* of RFC5646).
*
* @param languageTag the IETF BCP 47 language tag.
* @throws IllformedLocaleException if {@code languageTag} is not well-formed
* @throws NullPointerException if {@code languageTag} is {@code null}
* @spec https://www.rfc-editor.org/rfc/rfc5646.html#section-2.1
* RFC5646 2.1. Syntax
* @spec https://www.rfc-editor.org/rfc/rfc5646#section-2.1.1
* RFC5646 2.1.1. Formatting of Language Tags
* @since 21
*/
public static String caseFoldLanguageTag(String languageTag) {
return LanguageTag.caseFoldTag(languageTag);
}
/**
* Returns a locale for the specified IETF BCP 47 language tag string.
*
@ -1748,7 +1800,7 @@ public final class Locale implements Cloneable, Serializable {
* // returns "th-TH-u-nu-thai-x-lvariant-TH"
* </pre></ul>
*
* <p>This implements the 'Language-Tag' production of BCP47, and
* <p id="legacy_tags">This implements the 'Language-Tag' production of BCP47, and
* so supports legacy (regular and irregular, referred to as
* "Type: grandfathered" in BCP47) as well as
* private use language tags. Stand alone private use tags are

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2010, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2010, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -34,7 +34,9 @@ package sun.util.locale;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.IllformedLocaleException;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;
@ -59,7 +61,6 @@ public class LanguageTag {
private List<String> extlangs = Collections.emptyList(); // extlang subtags
private List<String> variants = Collections.emptyList(); // variant subtags
private List<String> extensions = Collections.emptyList(); // extensions
// Map contains legacy language tags and its preferred mappings from
// http://www.ietf.org/rfc/rfc5646.txt
// Keys are lower-case strings.
@ -208,7 +209,6 @@ public class LanguageTag {
tag.parseExtensions(itr, sts);
}
tag.parsePrivateuse(itr, sts);
if (!itr.isDone() && !sts.isError()) {
String s = itr.current();
sts.errorIndex = itr.currentStart();
@ -218,7 +218,6 @@ public class LanguageTag {
sts.errorMsg = "Invalid subtag: " + s;
}
}
return tag;
}
@ -414,6 +413,54 @@ public class LanguageTag {
return found;
}
public static String caseFoldTag(String tag) {
ParseStatus sts = new ParseStatus();
parse(tag, sts);
// Illegal tags
if (sts.errorMsg != null) {
throw new IllformedLocaleException(String.format("Ill formed tag:" +
" %s", sts.errorMsg));
}
// Legacy tags
String potentialLegacy = tag.toLowerCase(Locale.ROOT);
if (LEGACY.containsKey(potentialLegacy)) {
return LEGACY.get(potentialLegacy)[0];
}
// Non-legacy tags
StringBuilder bldr = new StringBuilder(tag.length());
String[] subtags = tag.split("-");
boolean privateFound = false;
boolean singletonFound = false;
boolean privUseVarFound = false;
for (int i = 0; i < subtags.length; i++) {
String subtag = subtags[i];
if (privUseVarFound) {
bldr.append(subtag);
} else if (i > 0 && isVariant(subtag) && !singletonFound && !privateFound) {
bldr.append(subtag);
} else if (i > 0 && isRegion(subtag) && !singletonFound && !privateFound) {
bldr.append(canonicalizeRegion(subtag));
} else if (i > 0 && isScript(subtag) && !singletonFound && !privateFound) {
bldr.append(canonicalizeScript(subtag));
// If subtag is not 2 letter, 4 letter, or variant
// under the right conditions, then it should be lower-case
} else {
if (isPrivateusePrefix(subtag)) {
privateFound = true;
} else if (isExtensionSingleton(subtag)) {
singletonFound = true;
} else if (subtag.equals(PRIVUSE_VARIANT_PREFIX)) {
privUseVarFound = true;
}
bldr.append(subtag.toLowerCase(Locale.ROOT));
}
if (i != subtags.length-1) {
bldr.append("-");
}
}
return bldr.substring(0);
}
public static LanguageTag parseLocale(BaseLocale baseLocale, LocaleExtensions localeExtensions) {
LanguageTag tag = new LanguageTag();