8176841: Additional Unicode Language-Tag Extensions

8189134: New system properties for the default Locale extensions
8190918: Retrieve the region specific data regardless of language in locale
8191349: Add a new method in j.t.f.DateTimeFormatter to reflect Unicode extensions

Reviewed-by: scolebourne, lancea, rriggs, rgoel, nishjain
This commit is contained in:
Naoto Sato 2017-12-12 10:21:58 -08:00
parent 3246c46f41
commit f065141ddc
55 changed files with 3631 additions and 890 deletions

View file

@ -97,6 +97,13 @@ import sun.util.locale.provider.LocaleServiceProviderPool;
* DateFormat df = DateFormat.getDateInstance(DateFormat.LONG, Locale.FRANCE);
* }</pre>
* </blockquote>
*
* <p>If the specified locale contains "ca" (calendar), "rg" (region override),
* and/or "tz" (timezone) <a href="../util/Locale.html#def_locale_extension">Unicode
* extensions</a>, the calendar, the country and/or the time zone for formatting
* are overridden. If both "ca" and "rg" are specified, the calendar from the "ca"
* extension supersedes the implicit one from the "rg" extension.
*
* <p>You can use a DateFormat to parse also.
* <blockquote>
* <pre>{@code

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 1996, 2016, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1996, 2017, 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
@ -49,6 +49,7 @@ import java.util.Objects;
import java.util.ResourceBundle;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import sun.util.locale.provider.CalendarDataUtility;
import sun.util.locale.provider.LocaleProviderAdapter;
import sun.util.locale.provider.LocaleServiceProviderPool;
import sun.util.locale.provider.ResourceBundleBasedAdapter;
@ -82,6 +83,10 @@ import sun.util.locale.provider.TimeZoneNameUtility;
* </pre>
* </blockquote>
*
* <p>If the locale contains "rg" (region override)
* <a href="../util/Locale.html#def_locale_extension">Unicode extension</a>,
* the symbols are overridden for the designated region.
*
* <p>
* <code>DateFormatSymbols</code> objects are cloneable. When you obtain
* a <code>DateFormatSymbols</code> object, feel free to modify the
@ -716,15 +721,18 @@ public class DateFormatSymbols implements Serializable, Cloneable {
}
dfs = new DateFormatSymbols(false);
// check for region override
Locale override = CalendarDataUtility.findRegionOverride(locale);
// Initialize the fields from the ResourceBundle for locale.
LocaleProviderAdapter adapter
= LocaleProviderAdapter.getAdapter(DateFormatSymbolsProvider.class, locale);
= LocaleProviderAdapter.getAdapter(DateFormatSymbolsProvider.class, override);
// Avoid any potential recursions
if (!(adapter instanceof ResourceBundleBasedAdapter)) {
adapter = LocaleProviderAdapter.getResourceBundleBased();
}
ResourceBundle resource
= ((ResourceBundleBasedAdapter)adapter).getLocaleData().getDateFormatData(locale);
= ((ResourceBundleBasedAdapter)adapter).getLocaleData().getDateFormatData(override);
dfs.locale = locale;
// JRE and CLDR use different keys

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1996, 2017, 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
@ -44,6 +44,7 @@ import java.io.Serializable;
import java.text.spi.DecimalFormatSymbolsProvider;
import java.util.Currency;
import java.util.Locale;
import sun.util.locale.provider.CalendarDataUtility;
import sun.util.locale.provider.LocaleProviderAdapter;
import sun.util.locale.provider.LocaleServiceProviderPool;
import sun.util.locale.provider.ResourceBundleBasedAdapter;
@ -56,6 +57,10 @@ import sun.util.locale.provider.ResourceBundleBasedAdapter;
* of these symbols, you can get the <code>DecimalFormatSymbols</code> object from
* your <code>DecimalFormat</code> and modify it.
*
* <p>If the locale contains "rg" (region override)
* <a href="../util/Locale.html#def_locale_extension">Unicode extension</a>,
* the symbols are overridden for the designated region.
*
* @see java.util.Locale
* @see DecimalFormat
* @author Mark Davis
@ -609,13 +614,18 @@ public class DecimalFormatSymbols implements Cloneable, Serializable {
private void initialize( Locale locale ) {
this.locale = locale;
// check for region override
Locale override = locale.getUnicodeLocaleType("nu") == null ?
CalendarDataUtility.findRegionOverride(locale) :
locale;
// get resource bundle data
LocaleProviderAdapter adapter = LocaleProviderAdapter.getAdapter(DecimalFormatSymbolsProvider.class, locale);
LocaleProviderAdapter adapter = LocaleProviderAdapter.getAdapter(DecimalFormatSymbolsProvider.class, override);
// Avoid potential recursions
if (!(adapter instanceof ResourceBundleBasedAdapter)) {
adapter = LocaleProviderAdapter.getResourceBundleBased();
}
Object[] data = adapter.getLocaleResources(locale).getDecimalFormatSymbolsData();
Object[] data = adapter.getLocaleResources(override).getDecimalFormatSymbolsData();
String[] numberElements = (String[]) data[0];
decimalSeparator = numberElements[0].charAt(0);

View file

@ -96,7 +96,14 @@ import sun.util.locale.provider.LocaleServiceProviderPool;
* NumberFormat nf = NumberFormat.getInstance(Locale.FRENCH);
* }</pre>
* </blockquote>
* You can also use a <code>NumberFormat</code> to parse numbers:
*
* <p>If the locale contains "nu" (numbers) and/or "rg" (region override)
* <a href="../util/Locale.html#def_locale_extension">Unicode extensions</a>,
* the decimal digits, and/or the country used for formatting are overridden.
* If both "nu" and "rg" are specified, the decimal digits from the "nu"
* extension supersedes the implicit one from the "rg" extension.
*
* <p>You can also use a {@code NumberFormat} to parse numbers:
* <blockquote>
* <pre>{@code
* myNumber = nf.parse(myString);

View file

@ -672,7 +672,7 @@ public class SimpleDateFormat extends DateFormat {
// However, the calendar should use the current default TimeZone.
// If this is not contained in the locale zone strings, then the zone
// will be formatted using generic GMT+/-H:MM nomenclature.
calendar = Calendar.getInstance(TimeZone.getDefault(), loc);
calendar = Calendar.getInstance(loc);
}
}

View file

@ -97,6 +97,7 @@ import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import sun.util.locale.provider.TimeZoneNameUtility;
/**
* Formatter for printing and parsing date-time objects.
@ -548,7 +549,7 @@ public final class DateTimeFormatter {
* For example, {@code d MMM uuuu} will format 2011-12-03 as '3 Dec 2011'.
* <p>
* The formatter will use the {@link Locale#getDefault(Locale.Category) default FORMAT locale}.
* This can be changed using {@link DateTimeFormatter#withLocale(Locale)} on the returned formatter
* This can be changed using {@link DateTimeFormatter#withLocale(Locale)} on the returned formatter.
* Alternatively use the {@link #ofPattern(String, Locale)} variant of this method.
* <p>
* The returned formatter has no override chronology or zone.
@ -572,7 +573,7 @@ public final class DateTimeFormatter {
* For example, {@code d MMM uuuu} will format 2011-12-03 as '3 Dec 2011'.
* <p>
* The formatter will use the specified locale.
* This can be changed using {@link DateTimeFormatter#withLocale(Locale)} on the returned formatter
* This can be changed using {@link DateTimeFormatter#withLocale(Locale)} on the returned formatter.
* <p>
* The returned formatter has no override chronology or zone.
* It uses {@link ResolverStyle#SMART SMART} resolver style.
@ -1443,10 +1444,17 @@ public final class DateTimeFormatter {
* This is used to lookup any part of the formatter needing specific
* localization, such as the text or localized pattern.
* <p>
* The locale is stored as passed in, without further processing.
* If the locale has <a href="../../util/Locale.html#def_locale_extension">
* Unicode extensions</a>, they may be used later in text
* processing. To set the chronology, time-zone and decimal style from
* unicode extensions, see {@link #localizedBy localizedBy()}.
* <p>
* This instance is immutable and unaffected by this method call.
*
* @param locale the new locale, not null
* @return a formatter based on this formatter with the requested locale, not null
* @see #localizedBy(Locale)
*/
public DateTimeFormatter withLocale(Locale locale) {
if (this.locale.equals(locale)) {
@ -1455,6 +1463,52 @@ public final class DateTimeFormatter {
return new DateTimeFormatter(printerParser, locale, decimalStyle, resolverStyle, resolverFields, chrono, zone);
}
/**
* Returns a copy of this formatter with localized values of the locale,
* calendar, region, decimal style and/or timezone, that supercede values in
* this formatter.
* <p>
* This is used to lookup any part of the formatter needing specific
* localization, such as the text or localized pattern. If the locale contains the
* "ca" (calendar), "nu" (numbering system), "rg" (region override), and/or
* "tz" (timezone)
* <a href="../../util/Locale.html#def_locale_extension">Unicode extensions</a>,
* the chronology, numbering system and/or the zone are overridden. If both "ca"
* and "rg" are specified, the chronology from the "ca" extension supersedes the
* implicit one from the "rg" extension. Same is true for the "nu" extension.
* <p>
* Unlike the {@link #withLocale withLocale} method, the call to this method may
* produce a different formatter depending on the order of method chaining with
* other withXXXX() methods.
* <p>
* This instance is immutable and unaffected by this method call.
*
* @param locale the locale, not null
* @return a formatter based on this formatter with localized values of
* the calendar, decimal style and/or timezone, that supercede values in this
* formatter.
* @see #withLocale(Locale)
* @since 10
*/
public DateTimeFormatter localizedBy(Locale locale) {
if (this.locale.equals(locale)) {
return this;
}
// Check for decimalStyle/chronology/timezone in locale object
Chronology c = locale.getUnicodeLocaleType("ca") != null ?
Chronology.ofLocale(locale) : chrono;
DecimalStyle ds = locale.getUnicodeLocaleType("nu") != null ?
DecimalStyle.of(locale) : decimalStyle;
String tzType = locale.getUnicodeLocaleType("tz");
ZoneId z = tzType != null ?
TimeZoneNameUtility.convertLDMLShortID(tzType)
.map(ZoneId::of)
.orElse(zone) :
zone;
return new DateTimeFormatter(printerParser, locale, ds, resolverStyle, resolverFields, c, z);
}
//-----------------------------------------------------------------------
/**
* Gets the DecimalStyle to be used during formatting.

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2017, 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
@ -120,6 +120,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import sun.text.spi.JavaTimeDateTimePatternProvider;
import sun.util.locale.provider.CalendarDataUtility;
import sun.util.locale.provider.LocaleProviderAdapter;
import sun.util.locale.provider.LocaleResources;
import sun.util.locale.provider.TimeZoneNameUtility;
@ -198,6 +199,10 @@ public final class DateTimeFormatterBuilder {
* Gets the formatting pattern for date and time styles for a locale and chronology.
* The locale and chronology are used to lookup the locale specific format
* for the requested dateStyle and/or timeStyle.
* <p>
* If the locale contains the "rg" (region override)
* <a href="../../util/Locale.html#def_locale_extension">Unicode extensions</a>,
* the formatting pattern is overridden with the one appropriate for the region.
*
* @param dateStyle the FormatStyle for the date, null for time-only pattern
* @param timeStyle the FormatStyle for the time, null for date-only pattern
@ -216,7 +221,8 @@ public final class DateTimeFormatterBuilder {
LocaleProviderAdapter adapter = LocaleProviderAdapter.getAdapter(JavaTimeDateTimePatternProvider.class, locale);
JavaTimeDateTimePatternProvider provider = adapter.getJavaTimeDateTimePatternProvider();
String pattern = provider.getJavaTimeDateTimePattern(convertStyle(timeStyle),
convertStyle(dateStyle), chrono.getCalendarType(), locale);
convertStyle(dateStyle), chrono.getCalendarType(),
CalendarDataUtility.findRegionOverride(locale));
return pattern;
}

View file

@ -510,7 +510,8 @@ class DateTimeTextProvider {
@SuppressWarnings("unchecked")
static <T> T getLocalizedResource(String key, Locale locale) {
LocaleResources lr = LocaleProviderAdapter.getResourceBundleBased()
.getLocaleResources(locale);
.getLocaleResources(
CalendarDataUtility.findRegionOverride(locale));
ResourceBundle rb = lr.getJavaTimeFormatData();
return rb.containsKey(key) ? (T) rb.getObject(key) : null;
}

View file

@ -147,6 +147,11 @@ public final class DecimalStyle {
* Obtains the DecimalStyle for the specified locale.
* <p>
* This method provides access to locale sensitive decimal style symbols.
* If the locale contains "nu" (Numbering System) and/or "rg"
* (Region Override) <a href="../../util/Locale.html#def_locale_extension">
* Unicode extensions</a>, returned instance will reflect the values specified with
* those extensions. If both "nu" and "rg" are specified, the value from
* the "nu" extension supersedes the implicit one from the "rg" extension.
*
* @param locale the locale, not null
* @return the decimal style, not null

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2017, 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
@ -79,6 +79,7 @@ import java.time.chrono.Chronology;
import java.util.Locale;
import java.util.Objects;
import java.util.ResourceBundle;
import sun.util.locale.provider.CalendarDataUtility;
import sun.util.locale.provider.LocaleProviderAdapter;
import sun.util.locale.provider.LocaleResources;
@ -632,7 +633,9 @@ public enum ChronoField implements TemporalField {
}
LocaleResources lr = LocaleProviderAdapter.getResourceBundleBased()
.getLocaleResources(locale);
.getLocaleResources(
CalendarDataUtility
.findRegionOverride(locale));
ResourceBundle rb = lr.getJavaTimeFormatData();
String key = "field." + displayNameKey;
return rb.containsKey(key) ? rb.getString(key) : name;

View file

@ -81,6 +81,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.ResourceBundle;
import sun.util.locale.provider.CalendarDataUtility;
import sun.util.locale.provider.LocaleProviderAdapter;
import sun.util.locale.provider.LocaleResources;
@ -430,7 +431,9 @@ public final class IsoFields {
public String getDisplayName(Locale locale) {
Objects.requireNonNull(locale, "locale");
LocaleResources lr = LocaleProviderAdapter.getResourceBundleBased()
.getLocaleResources(locale);
.getLocaleResources(
CalendarDataUtility
.findRegionOverride(locale));
ResourceBundle rb = lr.getJavaTimeFormatData();
return rb.containsKey("field.week") ? rb.getString("field.week") : toString();
}

View file

@ -286,13 +286,17 @@ public final class WeekFields implements Serializable {
* Obtains an instance of {@code WeekFields} appropriate for a locale.
* <p>
* This will look up appropriate values from the provider of localization data.
* If the locale contains "fw" (First day of week) and/or "rg"
* (Region Override) <a href="../../util/Locale.html#def_locale_extension">
* Unicode extensions</a>, returned instance will reflect the values specified with
* those extensions. If both "fw" and "rg" are specified, the value from
* the "fw" extension supersedes the implicit one from the "rg" extension.
*
* @param locale the locale to use, not null
* @return the week-definition, not null
*/
public static WeekFields of(Locale locale) {
Objects.requireNonNull(locale, "locale");
locale = new Locale(locale.getLanguage(), locale.getCountry()); // elminate variants
int calDow = CalendarDataUtility.retrieveFirstDayOfWeek(locale);
DayOfWeek dow = DayOfWeek.SUNDAY.plus(calDow - 1);
@ -1041,7 +1045,8 @@ public final class WeekFields implements Serializable {
Objects.requireNonNull(locale, "locale");
if (rangeUnit == YEARS) { // only have values for week-of-year
LocaleResources lr = LocaleProviderAdapter.getResourceBundleBased()
.getLocaleResources(locale);
.getLocaleResources(
CalendarDataUtility.findRegionOverride(locale));
ResourceBundle rb = lr.getJavaTimeFormatData();
return rb.containsKey("field.week") ? rb.getString("field.week") : name;
}

View file

@ -58,6 +58,7 @@ import sun.util.BuddhistCalendar;
import sun.util.calendar.ZoneInfo;
import sun.util.locale.provider.CalendarDataUtility;
import sun.util.locale.provider.LocaleProviderAdapter;
import sun.util.locale.provider.TimeZoneNameUtility;
import sun.util.spi.CalendarProvider;
/**
@ -128,9 +129,14 @@ import sun.util.spi.CalendarProvider;
*
* <code>Calendar</code> defines a locale-specific seven day week using two
* parameters: the first day of the week and the minimal days in first week
* (from 1 to 7). These numbers are taken from the locale resource data when a
* <code>Calendar</code> is constructed. They may also be specified explicitly
* through the methods for setting their values.
* (from 1 to 7). These numbers are taken from the locale resource data or the
* locale itself when a {@code Calendar} is constructed. If the designated
* locale contains "fw" and/or "rg" <a href="./Locale.html#def_locale_extension">
* Unicode extensions</a>, the first day of the week will be obtained according to
* those extensions. If both "fw" and "rg" are specified, the value from the "fw"
* extension supersedes the implicit one from the "rg" extension.
* They may also be specified explicitly through the methods for setting their
* values.
*
* <p>When setting or getting the <code>WEEK_OF_MONTH</code> or
* <code>WEEK_OF_YEAR</code> fields, <code>Calendar</code> must determine the
@ -1444,6 +1450,11 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable<Ca
*
* <p>The default values are used for locale and time zone if these
* parameters haven't been given explicitly.
* <p>
* If the locale contains the time zone with "tz"
* <a href="Locale.html#def_locale_extension">Unicode extension</a>,
* and time zone hasn't been given explicitly, time zone in the locale
* is used.
*
* <p>Any out of range field values are either normalized in lenient
* mode or detected as an invalid value in non-lenient mode.
@ -1463,7 +1474,7 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable<Ca
locale = Locale.getDefault();
}
if (zone == null) {
zone = TimeZone.getDefault();
zone = defaultTimeZone(locale);
}
Calendar cal;
if (type == null) {
@ -1605,12 +1616,17 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable<Ca
* <code>Calendar</code> returned is based on the current time
* in the default time zone with the default
* {@link Locale.Category#FORMAT FORMAT} locale.
* <p>
* If the locale contains the time zone with "tz"
* <a href="Locale.html#def_locale_extension">Unicode extension</a>,
* that time zone is used instead.
*
* @return a Calendar.
*/
public static Calendar getInstance()
{
return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
Locale aLocale = Locale.getDefault(Locale.Category.FORMAT);
return createCalendar(defaultTimeZone(aLocale), aLocale);
}
/**
@ -1631,13 +1647,17 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable<Ca
* Gets a calendar using the default time zone and specified locale.
* The <code>Calendar</code> returned is based on the current time
* in the default time zone with the given locale.
* <p>
* If the locale contains the time zone with "tz"
* <a href="Locale.html#def_locale_extension">Unicode extension</a>,
* that time zone is used instead.
*
* @param aLocale the locale for the week data
* @return a Calendar.
*/
public static Calendar getInstance(Locale aLocale)
{
return createCalendar(TimeZone.getDefault(), aLocale);
return createCalendar(defaultTimeZone(aLocale), aLocale);
}
/**
@ -1655,6 +1675,16 @@ public abstract class Calendar implements Serializable, Cloneable, Comparable<Ca
return createCalendar(zone, aLocale);
}
private static TimeZone defaultTimeZone(Locale l) {
TimeZone defaultTZ = TimeZone.getDefault();
String shortTZID = l.getUnicodeLocaleType("tz");
return shortTZID != null ?
TimeZoneNameUtility.convertLDMLShortID(shortTZID)
.map(TimeZone::getTimeZone)
.orElse(defaultTZ) :
defaultTZ;
}
private static Calendar createCalendar(TimeZone zone,
Locale aLocale)
{

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2000, 2017, 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
@ -28,7 +28,6 @@ package java.util;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.InputStream;
import java.io.IOException;
@ -42,6 +41,7 @@ import java.util.concurrent.ConcurrentMap;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.spi.CurrencyNameProvider;
import sun.util.locale.provider.CalendarDataUtility;
import sun.util.locale.provider.LocaleServiceProviderPool;
import sun.util.logging.PlatformLogger;
@ -348,6 +348,13 @@ public final class Currency implements Serializable {
* until December 31, 2001, and the Euro from January 1, 2002, local time
* of the respective countries.
* <p>
* If the specified {@code locale} contains "cu" and/or "rg"
* <a href="./Locale.html#def_locale_extension">Unicode extensions</a>,
* the instance returned from this method reflects
* the values specified with those extensions. If both "cu" and "rg" are
* specified, the currency from the "cu" extension supersedes the implicit one
* from the "rg" extension.
* <p>
* The method returns <code>null</code> for territories that don't
* have a currency, such as Antarctica.
*
@ -361,12 +368,19 @@ public final class Currency implements Serializable {
* is not a supported ISO 3166 country code.
*/
public static Currency getInstance(Locale locale) {
String country = locale.getCountry();
if (country == null) {
throw new NullPointerException();
// check for locale overrides
String override = locale.getUnicodeLocaleType("cu");
if (override != null) {
try {
return getInstance(override.toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException iae) {
// override currency is invalid. Fall through.
}
}
if (country.length() != 2) {
String country = CalendarDataUtility.findRegionOverride(locale).getCountry();
if (country == null || !country.matches("^[a-zA-Z]{2}$")) {
throw new IllegalArgumentException();
}
@ -482,6 +496,12 @@ public final class Currency implements Serializable {
* locale is the US, while for other locales it may be "US$". If no
* symbol can be determined, the ISO 4217 currency code is returned.
* <p>
* If the default {@link Locale.Category#DISPLAY DISPLAY} locale
* contains "rg" (region override)
* <a href="./Locale.html#def_locale_extension">Unicode extension</a>,
* the symbol returned from this method reflects
* the value specified with that extension.
* <p>
* This is equivalent to calling
* {@link #getSymbol(Locale)
* getSymbol(Locale.getDefault(Locale.Category.DISPLAY))}.
@ -498,6 +518,11 @@ public final class Currency implements Serializable {
* For example, for the US Dollar, the symbol is "$" if the specified
* locale is the US, while for other locales it may be "US$". If no
* symbol can be determined, the ISO 4217 currency code is returned.
* <p>
* If the specified {@code locale} contains "rg" (region override)
* <a href="./Locale.html#def_locale_extension">Unicode extension</a>,
* the symbol returned from this method reflects
* the value specified with that extension.
*
* @param locale the locale for which a display name for this currency is
* needed
@ -507,6 +532,7 @@ public final class Currency implements Serializable {
public String getSymbol(Locale locale) {
LocaleServiceProviderPool pool =
LocaleServiceProviderPool.getPool(CurrencyNameProvider.class);
locale = CalendarDataUtility.findRegionOverride(locale);
String symbol = pool.getLocalizedObject(
CurrencyNameGetter.INSTANCE,
locale, currencyCode, SYMBOL);

View file

@ -48,6 +48,7 @@ import java.io.Serializable;
import java.text.MessageFormat;
import java.util.concurrent.ConcurrentHashMap;
import java.util.spi.LocaleNameProvider;
import java.util.stream.Collectors;
import sun.security.action.GetPropertyAction;
import sun.util.locale.BaseLocale;
@ -62,6 +63,7 @@ import sun.util.locale.ParseStatus;
import sun.util.locale.provider.LocaleProviderAdapter;
import sun.util.locale.provider.LocaleResources;
import sun.util.locale.provider.LocaleServiceProviderPool;
import sun.util.locale.provider.TimeZoneNameUtility;
/**
* A <code>Locale</code> object represents a specific geographical, political,
@ -665,10 +667,12 @@ public final class Locale implements Cloneable, Serializable {
/**
* Display types for retrieving localized names from the name providers.
*/
private static final int DISPLAY_LANGUAGE = 0;
private static final int DISPLAY_COUNTRY = 1;
private static final int DISPLAY_VARIANT = 2;
private static final int DISPLAY_SCRIPT = 3;
private static final int DISPLAY_LANGUAGE = 0;
private static final int DISPLAY_COUNTRY = 1;
private static final int DISPLAY_VARIANT = 2;
private static final int DISPLAY_SCRIPT = 3;
private static final int DISPLAY_UEXT_KEY = 4;
private static final int DISPLAY_UEXT_TYPE = 5;
/**
* Private constructor used by getInstance method
@ -942,11 +946,14 @@ public final class Locale implements Cloneable, Serializable {
variant = props.getProperty("user.variant", "");
}
return getInstance(language, script, country, variant, null);
return getInstance(language, script, country, variant,
getDefaultExtensions(props.getProperty("user.extensions", ""))
.orElse(null));
}
private static Locale initDefault(Locale.Category category) {
Properties props = GetPropertyAction.privilegedGetProperties();
return getInstance(
props.getProperty(category.languageKey,
defaultLocale.getLanguage()),
@ -956,7 +963,22 @@ public final class Locale implements Cloneable, Serializable {
defaultLocale.getCountry()),
props.getProperty(category.variantKey,
defaultLocale.getVariant()),
null);
getDefaultExtensions(props.getProperty(category.extensionsKey, ""))
.orElse(defaultLocale.getLocaleExtensions()));
}
private static Optional<LocaleExtensions> getDefaultExtensions(String extensionsProp) {
LocaleExtensions exts = null;
try {
exts = new InternalLocaleBuilder()
.setExtensions(extensionsProp)
.getLocaleExtensions();
} catch (LocaleSyntaxException e) {
// just ignore this incorrect property
}
return Optional.ofNullable(exts);
}
/**
@ -1771,7 +1793,7 @@ public final class Locale implements Cloneable, Serializable {
* @exception NullPointerException if <code>inLocale</code> is <code>null</code>
*/
public String getDisplayLanguage(Locale inLocale) {
return getDisplayString(baseLocale.getLanguage(), inLocale, DISPLAY_LANGUAGE);
return getDisplayString(baseLocale.getLanguage(), null, inLocale, DISPLAY_LANGUAGE);
}
/**
@ -1801,7 +1823,7 @@ public final class Locale implements Cloneable, Serializable {
* @since 1.7
*/
public String getDisplayScript(Locale inLocale) {
return getDisplayString(baseLocale.getScript(), inLocale, DISPLAY_SCRIPT);
return getDisplayString(baseLocale.getScript(), null, inLocale, DISPLAY_SCRIPT);
}
/**
@ -1844,29 +1866,24 @@ public final class Locale implements Cloneable, Serializable {
* @exception NullPointerException if <code>inLocale</code> is <code>null</code>
*/
public String getDisplayCountry(Locale inLocale) {
return getDisplayString(baseLocale.getRegion(), inLocale, DISPLAY_COUNTRY);
return getDisplayString(baseLocale.getRegion(), null, inLocale, DISPLAY_COUNTRY);
}
private String getDisplayString(String code, Locale inLocale, int type) {
if (code.length() == 0) {
return "";
}
private String getDisplayString(String code, String cat, Locale inLocale, int type) {
Objects.requireNonNull(inLocale);
Objects.requireNonNull(code);
if (inLocale == null) {
throw new NullPointerException();
if (code.isEmpty()) {
return "";
}
LocaleServiceProviderPool pool =
LocaleServiceProviderPool.getPool(LocaleNameProvider.class);
String key = (type == DISPLAY_VARIANT ? "%%"+code : code);
String rbKey = (type == DISPLAY_VARIANT ? "%%"+code : code);
String result = pool.getLocalizedObject(
LocaleNameGetter.INSTANCE,
inLocale, key, type, code);
if (result != null) {
return result;
}
return code;
inLocale, rbKey, type, code, cat);
return result != null ? result : code;
}
/**
@ -1894,29 +1911,31 @@ public final class Locale implements Cloneable, Serializable {
if (baseLocale.getVariant().length() == 0)
return "";
LocaleResources lr = LocaleProviderAdapter.forJRE().getLocaleResources(inLocale);
LocaleResources lr = LocaleProviderAdapter
.getResourceBundleBased()
.getLocaleResources(inLocale);
String names[] = getDisplayVariantArray(inLocale);
// Get the localized patterns for formatting a list, and use
// them to format the list.
return formatList(names,
lr.getLocaleName("ListPattern"),
lr.getLocaleName("ListCompositionPattern"));
}
/**
* Returns a name for the locale that is appropriate for display to the
* user. This will be the values returned by getDisplayLanguage(),
* getDisplayScript(), getDisplayCountry(), and getDisplayVariant() assembled
* into a single string. The non-empty values are used in order,
* with the second and subsequent names in parentheses. For example:
* getDisplayScript(), getDisplayCountry(), getDisplayVariant() and
* optional <a href="./Locale.html#def_locale_extension">Unicode extensions</a>
* assembled into a single string. The non-empty values are used in order, with
* the second and subsequent names in parentheses. For example:
* <blockquote>
* language (script, country, variant)<br>
* language (country)<br>
* language (variant)<br>
* script (country)<br>
* country<br>
* language (script, country, variant(, extension)*)<br>
* language (country(, extension)*)<br>
* language (variant(, extension)*)<br>
* script (country(, extension)*)<br>
* country (extension)*<br>
* </blockquote>
* depending on which fields are specified in the locale. If the
* language, script, country, and variant fields are all empty,
@ -1931,16 +1950,17 @@ public final class Locale implements Cloneable, Serializable {
/**
* Returns a name for the locale that is appropriate for display
* to the user. This will be the values returned by
* getDisplayLanguage(), getDisplayScript(),getDisplayCountry(),
* and getDisplayVariant() assembled into a single string.
* The non-empty values are used in order,
* with the second and subsequent names in parentheses. For example:
* getDisplayLanguage(), getDisplayScript(),getDisplayCountry()
* getDisplayVariant(), and optional <a href="./Locale.html#def_locale_extension">
* Unicode extensions</a> assembled into a single string. The non-empty
* values are used in order, with the second and subsequent names in
* parentheses. For example:
* <blockquote>
* language (script, country, variant)<br>
* language (country)<br>
* language (variant)<br>
* script (country)<br>
* country<br>
* language (script, country, variant(, extension)*)<br>
* language (country(, extension)*)<br>
* language (variant(, extension)*)<br>
* script (country(, extension)*)<br>
* country (extension)*<br>
* </blockquote>
* depending on which fields are specified in the locale. If the
* language, script, country, and variant fields are all empty,
@ -1951,7 +1971,9 @@ public final class Locale implements Cloneable, Serializable {
* @throws NullPointerException if <code>inLocale</code> is <code>null</code>
*/
public String getDisplayName(Locale inLocale) {
LocaleResources lr = LocaleProviderAdapter.forJRE().getLocaleResources(inLocale);
LocaleResources lr = LocaleProviderAdapter
.getResourceBundleBased()
.getLocaleResources(inLocale);
String languageName = getDisplayLanguage(inLocale);
String scriptName = getDisplayScript(inLocale);
@ -1960,7 +1982,6 @@ public final class Locale implements Cloneable, Serializable {
// Get the localized patterns for formatting a display name.
String displayNamePattern = lr.getLocaleName("DisplayNamePattern");
String listPattern = lr.getLocaleName("ListPattern");
String listCompositionPattern = lr.getLocaleName("ListCompositionPattern");
// The display name consists of a main name, followed by qualifiers.
@ -1977,7 +1998,7 @@ public final class Locale implements Cloneable, Serializable {
if (variantNames.length == 0) {
return "";
} else {
return formatList(variantNames, listPattern, listCompositionPattern);
return formatList(variantNames, listCompositionPattern);
}
}
ArrayList<String> names = new ArrayList<>(4);
@ -1994,6 +2015,16 @@ public final class Locale implements Cloneable, Serializable {
names.addAll(Arrays.asList(variantNames));
}
// add Unicode extensions
if (localeExtensions != null) {
localeExtensions.getUnicodeLocaleAttributes().stream()
.map(key -> getDisplayString(key, null, inLocale, DISPLAY_UEXT_KEY))
.forEach(names::add);
localeExtensions.getUnicodeLocaleKeys().stream()
.map(key -> getDisplayKeyTypeExtensionString(key, lr, inLocale))
.forEach(names::add);
}
// The first one in the main name
mainName = names.get(0);
@ -2014,7 +2045,7 @@ public final class Locale implements Cloneable, Serializable {
// list case, but this is more efficient, and we want it to be
// efficient since all the language-only locales will not have any
// qualifiers.
qualifierNames.length != 0 ? formatList(qualifierNames, listPattern, listCompositionPattern) : null
qualifierNames.length != 0 ? formatList(qualifierNames, listCompositionPattern) : null
};
if (displayNamePattern != null) {
@ -2121,74 +2152,78 @@ public final class Locale implements Cloneable, Serializable {
// For each variant token, lookup the display name. If
// not found, use the variant name itself.
for (int i=0; i<names.length; ++i) {
names[i] = getDisplayString(tokenizer.nextToken(),
names[i] = getDisplayString(tokenizer.nextToken(), null,
inLocale, DISPLAY_VARIANT);
}
return names;
}
private String getDisplayKeyTypeExtensionString(String key, LocaleResources lr, Locale inLocale) {
String type = localeExtensions.getUnicodeLocaleType(key);
String ret = getDisplayString(type, key, inLocale, DISPLAY_UEXT_TYPE);
if (ret == null || ret.equals(type)) {
// no localization for this type. try combining key/type separately
String displayType = type;
switch (key) {
case "cu":
displayType = lr.getCurrencyName(type.toLowerCase(Locale.ROOT));
break;
case "rg":
if (type != null &&
// UN M.49 code should not be allowed here
type.matches("^[a-zA-Z]{2}[zZ]{4}$")) {
displayType = lr.getLocaleName(type.substring(0, 2).toUpperCase(Locale.ROOT));
}
break;
case "tz":
displayType = TimeZoneNameUtility.retrieveGenericDisplayName(
TimeZoneNameUtility.convertLDMLShortID(type).orElse(type),
TimeZone.LONG, inLocale);
break;
}
ret = MessageFormat.format(lr.getLocaleName("ListKeyTypePattern"),
getDisplayString(key, null, inLocale, DISPLAY_UEXT_KEY),
Optional.ofNullable(displayType).orElse(type));
}
return ret;
}
/**
* Format a list using given pattern strings.
* If either of the patterns is null, then a the list is
* formatted by concatenation with the delimiter ','.
* @param stringList the list of strings to be formatted.
* @param listPattern should create a MessageFormat taking 0-3 arguments
* and formatting them into a list.
* @param listCompositionPattern should take 2 arguments
* and is used by composeList.
* @param pattern should take 2 arguments for reduction
* @return a string representing the list.
*/
private static String formatList(String[] stringList, String listPattern, String listCompositionPattern) {
private static String formatList(String[] stringList, String pattern) {
// If we have no list patterns, compose the list in a simple,
// non-localized way.
if (listPattern == null || listCompositionPattern == null) {
StringJoiner sj = new StringJoiner(",");
for (int i = 0; i < stringList.length; ++i) {
sj.add(stringList[i]);
}
return sj.toString();
if (pattern == null) {
return Arrays.stream(stringList).collect(Collectors.joining(","));
}
// Compose the list down to three elements if necessary
if (stringList.length > 3) {
MessageFormat format = new MessageFormat(listCompositionPattern);
stringList = composeList(format, stringList);
switch (stringList.length) {
case 0:
return "";
case 1:
return stringList[0];
default:
return Arrays.stream(stringList).reduce("",
(s1, s2) -> {
if (s1.equals("")) {
return s2;
}
if (s2.equals("")) {
return s1;
}
return MessageFormat.format(pattern, s1, s2);
});
}
// Rebuild the argument list with the list length as the first element
Object[] args = new Object[stringList.length + 1];
System.arraycopy(stringList, 0, args, 1, stringList.length);
args[0] = stringList.length;
// Format it using the pattern in the resource
MessageFormat format = new MessageFormat(listPattern);
return format.format(args);
}
/**
* Given a list of strings, return a list shortened to three elements.
* Shorten it by applying the given format to the first two elements
* recursively.
* @param format a format which takes two arguments
* @param list a list of strings
* @return if the list is three elements or shorter, the same list;
* otherwise, a new list of three elements.
*/
private static String[] composeList(MessageFormat format, String[] list) {
if (list.length <= 3) return list;
// Use the given format to compose the first two elements into one
String[] listItems = { list[0], list[1] };
String newItem = format.format(listItems);
// Form a new list one element shorter
String[] newList = new String[list.length-1];
System.arraycopy(list, 2, newList, 1, newList.length-1);
newList[0] = newItem;
// Recurse
return composeList(format, newList);
}
// Duplicate of sun.util.locale.UnicodeLocaleExtension.isKey in order to
@ -2345,9 +2380,10 @@ public final class Locale implements Cloneable, Serializable {
Locale locale,
String key,
Object... params) {
assert params.length == 2;
assert params.length == 3;
int type = (Integer)params[0];
String code = (String)params[1];
String cat = (String)params[2];
switch(type) {
case DISPLAY_LANGUAGE:
@ -2358,6 +2394,10 @@ public final class Locale implements Cloneable, Serializable {
return localeNameProvider.getDisplayVariant(code, locale);
case DISPLAY_SCRIPT:
return localeNameProvider.getDisplayScript(code, locale);
case DISPLAY_UEXT_KEY:
return localeNameProvider.getDisplayUnicodeExtensionKey(code, locale);
case DISPLAY_UEXT_TYPE:
return localeNameProvider.getDisplayUnicodeExtensionType(code, cat, locale);
default:
assert false; // shouldn't happen
}
@ -2384,7 +2424,8 @@ public final class Locale implements Cloneable, Serializable {
DISPLAY("user.language.display",
"user.script.display",
"user.country.display",
"user.variant.display"),
"user.variant.display",
"user.extensions.display"),
/**
* Category used to represent the default locale for
@ -2393,19 +2434,23 @@ public final class Locale implements Cloneable, Serializable {
FORMAT("user.language.format",
"user.script.format",
"user.country.format",
"user.variant.format");
"user.variant.format",
"user.extensions.format");
Category(String languageKey, String scriptKey, String countryKey, String variantKey) {
Category(String languageKey, String scriptKey, String countryKey,
String variantKey, String extensionsKey) {
this.languageKey = languageKey;
this.scriptKey = scriptKey;
this.countryKey = countryKey;
this.variantKey = variantKey;
this.extensionsKey = extensionsKey;
}
final String languageKey;
final String scriptKey;
final String countryKey;
final String variantKey;
final String extensionsKey;
}
/**

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2005, 2017, 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
@ -26,6 +26,7 @@
package java.util.spi;
import java.util.Locale;
import java.util.Objects;
/**
* An abstract class for service providers that
@ -141,4 +142,54 @@ public abstract class LocaleNameProvider extends LocaleServiceProvider {
* @see java.util.Locale#getDisplayVariant(java.util.Locale)
*/
public abstract String getDisplayVariant(String variant, Locale locale);
/**
* Returns a localized name for the given
* <a href="../Locale.html#def_locale_extension">Unicode extension</a> key,
* and the given locale that is appropriate for display to the user.
* If the name returned cannot be localized according to {@code locale},
* this method returns null.
* @implSpec the default implementation returns {@code null}.
* @param key the Unicode Extension key, not null.
* @param locale the desired locale, not null.
* @return the name of the given key string for the specified locale,
* or null if it's not available.
* @exception NullPointerException if {@code key} or {@code locale} is null
* @exception IllegalArgumentException if {@code locale} isn't
* one of the locales returned from
* {@link java.util.spi.LocaleServiceProvider#getAvailableLocales()
* getAvailableLocales()}.
* @since 10
*/
public String getDisplayUnicodeExtensionKey(String key, Locale locale) {
Objects.requireNonNull(key);
Objects.requireNonNull(locale);
return null;
}
/**
* Returns a localized name for the given
* <a href="../Locale.html#def_locale_extension">Unicode extension</a> type,
* and the given locale that is appropriate for display to the user.
* If the name returned cannot be localized according to {@code locale},
* this method returns null.
* @implSpec the default implementation returns {@code null}.
* @param type the Unicode Extension type, not null.
* @param key the Unicode Extension key for this {@code type}, not null.
* @param locale the desired locale, not null.
* @return the name of the given type string for the specified locale,
* or null if it's not available.
* @exception NullPointerException if {@code key}, {@code type} or {@code locale} is null
* @exception IllegalArgumentException if {@code locale} isn't
* one of the locales returned from
* {@link java.util.spi.LocaleServiceProvider#getAvailableLocales()
* getAvailableLocales()}.
* @since 10
*/
public String getDisplayUnicodeExtensionType(String type, String key, Locale locale) {
Objects.requireNonNull(type);
Objects.requireNonNull(key);
Objects.requireNonNull(locale);
return null;
}
}