Add and use LocaleUtils.toLocale(Locale) to avoid NPEs and use the

default locale when an input locale is null.
This commit is contained in:
Gary Gregory 2020-12-21 12:56:19 -05:00
parent b5de0ef280
commit c9e825e823
9 changed files with 69 additions and 37 deletions

View File

@ -81,6 +81,7 @@ The <action> type attribute can be add,update,fix,remove.
<action issue="LANG-1596" type="update" dev="aherbert" due-to="Richard Eckart de Castilho">ArrayUtils.toPrimitive(Object) does not support boolean and other types #607.</action> <action issue="LANG-1596" type="update" dev="aherbert" due-to="Richard Eckart de Castilho">ArrayUtils.toPrimitive(Object) does not support boolean and other types #607.</action>
<action type="add" dev="ggregory" due-to="Gary Gregory">Add fluent-style ArrayUtils.sort(Object[]).</action> <action type="add" dev="ggregory" due-to="Gary Gregory">Add fluent-style ArrayUtils.sort(Object[]).</action>
<action type="add" dev="ggregory" due-to="Gary Gregory">Add fluent-style ArrayUtils.sort(Object[], Comparable).</action> <action type="add" dev="ggregory" due-to="Gary Gregory">Add fluent-style ArrayUtils.sort(Object[], Comparable).</action>
<action type="add" dev="ggregory" due-to="Gary Gregory">Add and use LocaleUtils.toLocale(Locale) to avoid NPEs.</action>
<!-- UPDATES --> <!-- UPDATES -->
<action type="update" dev="ggregory" due-to="Gary Gregory">Enable Dependabot #587.</action> <action type="update" dev="ggregory" due-to="Gary Gregory">Enable Dependabot #587.</action>
<action type="update" dev="chtompki">Bump junit-jupiter from 5.6.2 to 5.7.0.</action> <action type="update" dev="chtompki">Bump junit-jupiter from 5.6.2 to 5.7.0.</action>

View File

@ -276,7 +276,17 @@ private static Locale parseLocale(final String str) {
throw new IllegalArgumentException("Invalid locale format: " + str); throw new IllegalArgumentException("Invalid locale format: " + str);
} }
//----------------------------------------------------------------------- /**
* Returns the given locale if non-{@code null}, otherwise {@link Locale#getDefault()}.
*
* @param locale a locale or {@code null}.
* @return the given locale if non-{@code null}, otherwise {@link Locale#getDefault()}.
* @since 3.12
*/
public static Locale toLocale(final Locale locale) {
return locale != null ? locale : Locale.getDefault();
}
/** /**
* <p>Converts a String to a Locale.</p> * <p>Converts a String to a Locale.</p>
* *

View File

@ -5406,7 +5406,7 @@ public static String lowerCase(final String str, final Locale locale) {
if (str == null) { if (str == null) {
return null; return null;
} }
return str.toLowerCase(locale); return str.toLowerCase(LocaleUtils.toLocale(locale));
} }
private static int[] matches(final CharSequence first, final CharSequence second) { private static int[] matches(final CharSequence first, final CharSequence second) {
@ -9414,7 +9414,7 @@ public static String upperCase(final String str, final Locale locale) {
if (str == null) { if (str == null) {
return null; return null;
} }
return str.toUpperCase(locale); return str.toUpperCase(LocaleUtils.toLocale(locale));
} }
/** /**

View File

@ -26,6 +26,7 @@
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import org.apache.commons.lang3.LocaleUtils;
import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
@ -118,14 +119,14 @@ public ExtendedMessageFormat(final String pattern, final Map<String, ? extends F
/** /**
* Create a new ExtendedMessageFormat. * Create a new ExtendedMessageFormat.
* *
* @param pattern the pattern to use, not null * @param pattern the pattern to use, not null.
* @param locale the locale to use, not null * @param locale the locale to use.
* @param registry the registry of format factories, may be null * @param registry the registry of format factories, may be null.
* @throws IllegalArgumentException in case of a bad pattern. * @throws IllegalArgumentException in case of a bad pattern.
*/ */
public ExtendedMessageFormat(final String pattern, final Locale locale, final Map<String, ? extends FormatFactory> registry) { public ExtendedMessageFormat(final String pattern, final Locale locale, final Map<String, ? extends FormatFactory> registry) {
super(DUMMY_PATTERN); super(DUMMY_PATTERN);
setLocale(locale); setLocale(LocaleUtils.toLocale(locale));
this.registry = registry; this.registry = registry;
applyPattern(pattern); applyPattern(pattern);
} }

View File

@ -26,6 +26,7 @@
import java.util.TimeZone; import java.util.TimeZone;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.LocaleUtils;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
/** /**
@ -367,7 +368,7 @@ private static Date parseDateWithLeniency(final String str, final Locale locale,
} }
final TimeZone tz = TimeZone.getDefault(); final TimeZone tz = TimeZone.getDefault();
final Locale lcl = locale == null ? Locale.getDefault() : locale; final Locale lcl = LocaleUtils.toLocale(locale);
final ParsePosition pos = new ParsePosition(0); final ParsePosition pos = new ParsePosition(0);
final Calendar calendar = Calendar.getInstance(tz, lcl); final Calendar calendar = Calendar.getInstance(tz, lcl);
calendar.setLenient(lenient); calendar.setLenient(lenient);

View File

@ -39,6 +39,8 @@
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.apache.commons.lang3.LocaleUtils;
/** /**
* <p>FastDateParser is a fast and thread-safe version of * <p>FastDateParser is a fast and thread-safe version of
* {@link java.text.SimpleDateFormat}.</p> * {@link java.text.SimpleDateFormat}.</p>
@ -125,15 +127,15 @@ protected FastDateParser(final String pattern, final TimeZone timeZone, final Lo
protected FastDateParser(final String pattern, final TimeZone timeZone, final Locale locale, final Date centuryStart) { protected FastDateParser(final String pattern, final TimeZone timeZone, final Locale locale, final Date centuryStart) {
this.pattern = pattern; this.pattern = pattern;
this.timeZone = timeZone; this.timeZone = timeZone;
this.locale = locale; this.locale = LocaleUtils.toLocale(locale);
final Calendar definingCalendar = Calendar.getInstance(timeZone, locale); final Calendar definingCalendar = Calendar.getInstance(timeZone, this.locale);
int centuryStartYear; int centuryStartYear;
if (centuryStart!=null) { if (centuryStart!=null) {
definingCalendar.setTime(centuryStart); definingCalendar.setTime(centuryStart);
centuryStartYear = definingCalendar.get(Calendar.YEAR); centuryStartYear = definingCalendar.get(Calendar.YEAR);
} else if (locale.equals(JAPANESE_IMPERIAL)) { } else if (this.locale.equals(JAPANESE_IMPERIAL)) {
centuryStartYear = 0; centuryStartYear = 0;
} else { } else {
// from 80 years ago to 20 years from now // from 80 years ago to 20 years from now
@ -458,9 +460,9 @@ private static StringBuilder simpleQuote(final StringBuilder sb, final String va
* @param regex The regular expression to build * @param regex The regular expression to build
* @return The map of string display names to field values * @return The map of string display names to field values
*/ */
private static Map<String, Integer> appendDisplayNames(final Calendar cal, final Locale locale, final int field, final StringBuilder regex) { private static Map<String, Integer> appendDisplayNames(final Calendar cal, Locale locale, final int field, final StringBuilder regex) {
final Map<String, Integer> values = new HashMap<>(); final Map<String, Integer> values = new HashMap<>();
locale = LocaleUtils.toLocale(locale);
final Map<String, Integer> displayNames = cal.getDisplayNames(field, Calendar.ALL_STYLES, locale); final Map<String, Integer> displayNames = cal.getDisplayNames(field, Calendar.ALL_STYLES, locale);
final TreeSet<String> sorted = new TreeSet<>(LONGER_FIRST_LOWERCASE); final TreeSet<String> sorted = new TreeSet<>(LONGER_FIRST_LOWERCASE);
for (final Map.Entry<String, Integer> displayName : displayNames.entrySet()) { for (final Map.Entry<String, Integer> displayName : displayNames.entrySet()) {
@ -697,7 +699,7 @@ private static class CaseInsensitiveTextStrategy extends PatternStrategy {
*/ */
CaseInsensitiveTextStrategy(final int field, final Calendar definingCalendar, final Locale locale) { CaseInsensitiveTextStrategy(final int field, final Calendar definingCalendar, final Locale locale) {
this.field = field; this.field = field;
this.locale = locale; this.locale = LocaleUtils.toLocale(locale);
final StringBuilder regex = new StringBuilder(); final StringBuilder regex = new StringBuilder();
regex.append("((?iu)"); regex.append("((?iu)");
@ -837,7 +839,7 @@ private static class TzInfo {
* @param locale The Locale * @param locale The Locale
*/ */
TimeZoneStrategy(final Locale locale) { TimeZoneStrategy(final Locale locale) {
this.locale = locale; this.locale = LocaleUtils.toLocale(locale);
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
sb.append("((?iu)" + RFC_822_TIME_ZONE + "|" + GMT_OPTION ); sb.append("((?iu)" + RFC_822_TIME_ZONE + "|" + GMT_OPTION );

View File

@ -31,6 +31,7 @@
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import org.apache.commons.lang3.LocaleUtils;
import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.exception.ExceptionUtils;
/** /**
@ -150,7 +151,7 @@ public class FastDatePrinter implements DatePrinter, Serializable {
protected FastDatePrinter(final String pattern, final TimeZone timeZone, final Locale locale) { protected FastDatePrinter(final String pattern, final TimeZone timeZone, final Locale locale) {
mPattern = pattern; mPattern = pattern;
mTimeZone = timeZone; mTimeZone = timeZone;
mLocale = locale; mLocale = LocaleUtils.toLocale(locale);
init(); init();
} }
@ -1342,7 +1343,7 @@ private static class TimeZoneNameRule implements Rule {
* @param style the style * @param style the style
*/ */
TimeZoneNameRule(final TimeZone timeZone, final Locale locale, final int style) { TimeZoneNameRule(final TimeZone timeZone, final Locale locale, final int style) {
mLocale = locale; mLocale = LocaleUtils.toLocale(locale);
mStyle = style; mStyle = style;
mStandard = getTimeZoneDisplay(timeZone, false, style, locale); mStandard = getTimeZoneDisplay(timeZone, false, style, locale);
@ -1539,7 +1540,7 @@ private static class TimeZoneDisplayKey {
} else { } else {
mStyle = style; mStyle = style;
} }
mLocale = locale; mLocale = LocaleUtils.toLocale(locale);
} }
/** /**

View File

@ -25,6 +25,7 @@
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import org.apache.commons.lang3.LocaleUtils;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
/** /**
@ -73,9 +74,7 @@ public F getInstance(final String pattern, TimeZone timeZone, Locale locale) {
if (timeZone == null) { if (timeZone == null) {
timeZone = TimeZone.getDefault(); timeZone = TimeZone.getDefault();
} }
if (locale == null) { locale = LocaleUtils.toLocale(locale);
locale = Locale.getDefault();
}
final MultipartKey key = new MultipartKey(pattern, timeZone, locale); final MultipartKey key = new MultipartKey(pattern, timeZone, locale);
F format = cInstanceCache.get(key); F format = cInstanceCache.get(key);
if (format == null) { if (format == null) {
@ -118,9 +117,7 @@ public F getInstance(final String pattern, TimeZone timeZone, Locale locale) {
*/ */
// This must remain private, see LANG-884 // This must remain private, see LANG-884
private F getDateTimeInstance(final Integer dateStyle, final Integer timeStyle, final TimeZone timeZone, Locale locale) { private F getDateTimeInstance(final Integer dateStyle, final Integer timeStyle, final TimeZone timeZone, Locale locale) {
if (locale == null) { locale = LocaleUtils.toLocale(locale);
locale = Locale.getDefault();
}
final String pattern = getPatternForStyle(dateStyle, timeStyle, locale); final String pattern = getPatternForStyle(dateStyle, timeStyle, locale);
return getInstance(pattern, timeZone, locale); return getInstance(pattern, timeZone, locale);
} }
@ -188,18 +185,19 @@ F getTimeInstance(final int timeStyle, final TimeZone timeZone, final Locale loc
*/ */
// package protected, for access from test code; do not make public or protected // package protected, for access from test code; do not make public or protected
static String getPatternForStyle(final Integer dateStyle, final Integer timeStyle, final Locale locale) { static String getPatternForStyle(final Integer dateStyle, final Integer timeStyle, final Locale locale) {
final MultipartKey key = new MultipartKey(dateStyle, timeStyle, locale); final Locale safeLocale = LocaleUtils.toLocale(locale);
final MultipartKey key = new MultipartKey(dateStyle, timeStyle, safeLocale);
String pattern = cDateTimeInstanceCache.get(key); String pattern = cDateTimeInstanceCache.get(key);
if (pattern == null) { if (pattern == null) {
try { try {
DateFormat formatter; DateFormat formatter;
if (dateStyle == null) { if (dateStyle == null) {
formatter = DateFormat.getTimeInstance(timeStyle.intValue(), locale); formatter = DateFormat.getTimeInstance(timeStyle.intValue(), safeLocale);
} else if (timeStyle == null) { } else if (timeStyle == null) {
formatter = DateFormat.getDateInstance(dateStyle.intValue(), locale); formatter = DateFormat.getDateInstance(dateStyle.intValue(), safeLocale);
} else { } else {
formatter = DateFormat.getDateTimeInstance(dateStyle.intValue(), timeStyle.intValue(), locale); formatter = DateFormat.getDateTimeInstance(dateStyle.intValue(), timeStyle.intValue(), safeLocale);
} }
pattern = ((SimpleDateFormat) formatter).toPattern(); pattern = ((SimpleDateFormat) formatter).toPattern();
final String previous = cDateTimeInstanceCache.putIfAbsent(key, pattern); final String previous = cDateTimeInstanceCache.putIfAbsent(key, pattern);
@ -210,13 +208,12 @@ static String getPatternForStyle(final Integer dateStyle, final Integer timeStyl
pattern = previous; pattern = previous;
} }
} catch (final ClassCastException ex) { } catch (final ClassCastException ex) {
throw new IllegalArgumentException("No date time pattern for locale: " + locale); throw new IllegalArgumentException("No date time pattern for locale: " + safeLocale);
} }
} }
return pattern; return pattern;
} }
// ----------------------------------------------------------------------
/** /**
* <p>Helper class to hold multi-part Map keys</p> * <p>Helper class to hold multi-part Map keys</p>
*/ */

View File

@ -123,11 +123,30 @@ private static void assertValidToLocale(
} }
/** /**
* Test toLocale() method. * Test toLocale(Locale) method.
*/
@Test
public void testToLocale_Locale_defaults() {
assertNull(LocaleUtils.toLocale((String) null));
assertEquals(Locale.getDefault(), LocaleUtils.toLocale((Locale) null));
assertEquals(Locale.getDefault(), LocaleUtils.toLocale(Locale.getDefault()));
}
/**
* Test toLocale(Locale) method.
*/
@ParameterizedTest
@MethodSource("java.util.Locale#getAvailableLocales")
public void testToLocales(final Locale actualLocale) {
assertEquals(actualLocale, LocaleUtils.toLocale(actualLocale));
}
/**
* Test toLocale(String) method.
*/ */
@Test @Test
public void testToLocale_1Part() { public void testToLocale_1Part() {
assertNull(LocaleUtils.toLocale(null)); assertNull(LocaleUtils.toLocale((String) null));
assertValidToLocale("us"); assertValidToLocale("us");
assertValidToLocale("fr"); assertValidToLocale("fr");
@ -524,11 +543,11 @@ public void testLang865() {
@ParameterizedTest @ParameterizedTest
@MethodSource("java.util.Locale#getAvailableLocales") @MethodSource("java.util.Locale#getAvailableLocales")
public void testParseAllLocales(final Locale l) { public void testParseAllLocales(final Locale actualLocale) {
// Check if it's possible to recreate the Locale using just the standard constructor // Check if it's possible to recreate the Locale using just the standard constructor
final Locale locale = new Locale(l.getLanguage(), l.getCountry(), l.getVariant()); final Locale locale = new Locale(actualLocale.getLanguage(), actualLocale.getCountry(), actualLocale.getVariant());
if (l.equals(locale)) { // it is possible for LocaleUtils.toLocale to handle these Locales if (actualLocale.equals(locale)) { // it is possible for LocaleUtils.toLocale to handle these Locales
final String str = l.toString(); final String str = actualLocale.toString();
// Look for the script/extension suffix // Look for the script/extension suffix
int suff = str.indexOf("_#"); int suff = str.indexOf("_#");
if (suff == - 1) { if (suff == - 1) {
@ -541,7 +560,7 @@ public void testParseAllLocales(final Locale l) {
localeStr = str.substring(0, suff); localeStr = str.substring(0, suff);
} }
final Locale loc = LocaleUtils.toLocale(localeStr); final Locale loc = LocaleUtils.toLocale(localeStr);
assertEquals(l, loc); assertEquals(actualLocale, loc);
} }
} }
} }