From c9e825e823e30c5b1e3ddc9de5e8fd0094d52ee5 Mon Sep 17 00:00:00 2001 From: Gary Gregory Date: Mon, 21 Dec 2020 12:56:19 -0500 Subject: [PATCH] Add and use LocaleUtils.toLocale(Locale) to avoid NPEs and use the default locale when an input locale is null. --- src/changes/changes.xml | 1 + .../org/apache/commons/lang3/LocaleUtils.java | 12 ++++++- .../org/apache/commons/lang3/StringUtils.java | 4 +-- .../lang3/text/ExtendedMessageFormat.java | 9 ++--- .../apache/commons/lang3/time/DateUtils.java | 3 +- .../commons/lang3/time/FastDateParser.java | 16 +++++---- .../commons/lang3/time/FastDatePrinter.java | 7 ++-- .../commons/lang3/time/FormatCache.java | 21 +++++------- .../apache/commons/lang3/LocaleUtilsTest.java | 33 +++++++++++++++---- 9 files changed, 69 insertions(+), 37 deletions(-) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 25a2839a7..5804a9c56 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -81,6 +81,7 @@ The type attribute can be add,update,fix,remove. ArrayUtils.toPrimitive(Object) does not support boolean and other types #607. Add fluent-style ArrayUtils.sort(Object[]). Add fluent-style ArrayUtils.sort(Object[], Comparable). + Add and use LocaleUtils.toLocale(Locale) to avoid NPEs. Enable Dependabot #587. Bump junit-jupiter from 5.6.2 to 5.7.0. diff --git a/src/main/java/org/apache/commons/lang3/LocaleUtils.java b/src/main/java/org/apache/commons/lang3/LocaleUtils.java index 24b7f3e52..b73856e9a 100644 --- a/src/main/java/org/apache/commons/lang3/LocaleUtils.java +++ b/src/main/java/org/apache/commons/lang3/LocaleUtils.java @@ -276,7 +276,17 @@ private static Locale parseLocale(final String 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(); + } + /** *

Converts a String to a Locale.

* diff --git a/src/main/java/org/apache/commons/lang3/StringUtils.java b/src/main/java/org/apache/commons/lang3/StringUtils.java index ab9d9523b..a35b0022e 100644 --- a/src/main/java/org/apache/commons/lang3/StringUtils.java +++ b/src/main/java/org/apache/commons/lang3/StringUtils.java @@ -5406,7 +5406,7 @@ public static String lowerCase(final String str, final Locale locale) { if (str == null) { return null; } - return str.toLowerCase(locale); + return str.toLowerCase(LocaleUtils.toLocale(locale)); } 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) { return null; } - return str.toUpperCase(locale); + return str.toUpperCase(LocaleUtils.toLocale(locale)); } /** diff --git a/src/main/java/org/apache/commons/lang3/text/ExtendedMessageFormat.java b/src/main/java/org/apache/commons/lang3/text/ExtendedMessageFormat.java index 6b921b87a..c0cd9b311 100644 --- a/src/main/java/org/apache/commons/lang3/text/ExtendedMessageFormat.java +++ b/src/main/java/org/apache/commons/lang3/text/ExtendedMessageFormat.java @@ -26,6 +26,7 @@ import java.util.Map; import java.util.Objects; +import org.apache.commons.lang3.LocaleUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.Validate; @@ -118,14 +119,14 @@ public ExtendedMessageFormat(final String pattern, final Map registry) { super(DUMMY_PATTERN); - setLocale(locale); + setLocale(LocaleUtils.toLocale(locale)); this.registry = registry; applyPattern(pattern); } diff --git a/src/main/java/org/apache/commons/lang3/time/DateUtils.java b/src/main/java/org/apache/commons/lang3/time/DateUtils.java index fddfcbb3e..4b20c5443 100644 --- a/src/main/java/org/apache/commons/lang3/time/DateUtils.java +++ b/src/main/java/org/apache/commons/lang3/time/DateUtils.java @@ -26,6 +26,7 @@ import java.util.TimeZone; import java.util.concurrent.TimeUnit; +import org.apache.commons.lang3.LocaleUtils; 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 Locale lcl = locale == null ? Locale.getDefault() : locale; + final Locale lcl = LocaleUtils.toLocale(locale); final ParsePosition pos = new ParsePosition(0); final Calendar calendar = Calendar.getInstance(tz, lcl); calendar.setLenient(lenient); diff --git a/src/main/java/org/apache/commons/lang3/time/FastDateParser.java b/src/main/java/org/apache/commons/lang3/time/FastDateParser.java index e53a24607..9ae683b85 100644 --- a/src/main/java/org/apache/commons/lang3/time/FastDateParser.java +++ b/src/main/java/org/apache/commons/lang3/time/FastDateParser.java @@ -39,6 +39,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.apache.commons.lang3.LocaleUtils; + /** *

FastDateParser is a fast and thread-safe version of * {@link java.text.SimpleDateFormat}.

@@ -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) { this.pattern = pattern; 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; if (centuryStart!=null) { definingCalendar.setTime(centuryStart); centuryStartYear = definingCalendar.get(Calendar.YEAR); - } else if (locale.equals(JAPANESE_IMPERIAL)) { + } else if (this.locale.equals(JAPANESE_IMPERIAL)) { centuryStartYear = 0; } else { // 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 * @return The map of string display names to field values */ - private static Map appendDisplayNames(final Calendar cal, final Locale locale, final int field, final StringBuilder regex) { + private static Map appendDisplayNames(final Calendar cal, Locale locale, final int field, final StringBuilder regex) { final Map values = new HashMap<>(); - + locale = LocaleUtils.toLocale(locale); final Map displayNames = cal.getDisplayNames(field, Calendar.ALL_STYLES, locale); final TreeSet sorted = new TreeSet<>(LONGER_FIRST_LOWERCASE); for (final Map.Entry displayName : displayNames.entrySet()) { @@ -697,7 +699,7 @@ private static class CaseInsensitiveTextStrategy extends PatternStrategy { */ CaseInsensitiveTextStrategy(final int field, final Calendar definingCalendar, final Locale locale) { this.field = field; - this.locale = locale; + this.locale = LocaleUtils.toLocale(locale); final StringBuilder regex = new StringBuilder(); regex.append("((?iu)"); @@ -837,7 +839,7 @@ private static class TzInfo { * @param locale The Locale */ TimeZoneStrategy(final Locale locale) { - this.locale = locale; + this.locale = LocaleUtils.toLocale(locale); final StringBuilder sb = new StringBuilder(); sb.append("((?iu)" + RFC_822_TIME_ZONE + "|" + GMT_OPTION ); diff --git a/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java b/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java index 3402409cb..bc5cda5f7 100644 --- a/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java +++ b/src/main/java/org/apache/commons/lang3/time/FastDatePrinter.java @@ -31,6 +31,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import org.apache.commons.lang3.LocaleUtils; 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) { mPattern = pattern; mTimeZone = timeZone; - mLocale = locale; + mLocale = LocaleUtils.toLocale(locale); init(); } @@ -1342,7 +1343,7 @@ private static class TimeZoneNameRule implements Rule { * @param style the style */ TimeZoneNameRule(final TimeZone timeZone, final Locale locale, final int style) { - mLocale = locale; + mLocale = LocaleUtils.toLocale(locale); mStyle = style; mStandard = getTimeZoneDisplay(timeZone, false, style, locale); @@ -1539,7 +1540,7 @@ private static class TimeZoneDisplayKey { } else { mStyle = style; } - mLocale = locale; + mLocale = LocaleUtils.toLocale(locale); } /** diff --git a/src/main/java/org/apache/commons/lang3/time/FormatCache.java b/src/main/java/org/apache/commons/lang3/time/FormatCache.java index e1e3a0100..4786cd993 100644 --- a/src/main/java/org/apache/commons/lang3/time/FormatCache.java +++ b/src/main/java/org/apache/commons/lang3/time/FormatCache.java @@ -25,6 +25,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import org.apache.commons.lang3.LocaleUtils; import org.apache.commons.lang3.Validate; /** @@ -73,9 +74,7 @@ public F getInstance(final String pattern, TimeZone timeZone, Locale locale) { if (timeZone == null) { timeZone = TimeZone.getDefault(); } - if (locale == null) { - locale = Locale.getDefault(); - } + locale = LocaleUtils.toLocale(locale); final MultipartKey key = new MultipartKey(pattern, timeZone, locale); F format = cInstanceCache.get(key); if (format == null) { @@ -118,9 +117,7 @@ public F getInstance(final String pattern, TimeZone timeZone, Locale locale) { */ // This must remain private, see LANG-884 private F getDateTimeInstance(final Integer dateStyle, final Integer timeStyle, final TimeZone timeZone, Locale locale) { - if (locale == null) { - locale = Locale.getDefault(); - } + locale = LocaleUtils.toLocale(locale); final String pattern = getPatternForStyle(dateStyle, timeStyle, 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 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); if (pattern == null) { try { DateFormat formatter; if (dateStyle == null) { - formatter = DateFormat.getTimeInstance(timeStyle.intValue(), locale); + formatter = DateFormat.getTimeInstance(timeStyle.intValue(), safeLocale); } else if (timeStyle == null) { - formatter = DateFormat.getDateInstance(dateStyle.intValue(), locale); + formatter = DateFormat.getDateInstance(dateStyle.intValue(), safeLocale); } else { - formatter = DateFormat.getDateTimeInstance(dateStyle.intValue(), timeStyle.intValue(), locale); + formatter = DateFormat.getDateTimeInstance(dateStyle.intValue(), timeStyle.intValue(), safeLocale); } pattern = ((SimpleDateFormat) formatter).toPattern(); final String previous = cDateTimeInstanceCache.putIfAbsent(key, pattern); @@ -210,13 +208,12 @@ static String getPatternForStyle(final Integer dateStyle, final Integer timeStyl pattern = previous; } } 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; } - // ---------------------------------------------------------------------- /** *

Helper class to hold multi-part Map keys

*/ diff --git a/src/test/java/org/apache/commons/lang3/LocaleUtilsTest.java b/src/test/java/org/apache/commons/lang3/LocaleUtilsTest.java index 558b5f9cf..8a8513d03 100644 --- a/src/test/java/org/apache/commons/lang3/LocaleUtilsTest.java +++ b/src/test/java/org/apache/commons/lang3/LocaleUtilsTest.java @@ -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 public void testToLocale_1Part() { - assertNull(LocaleUtils.toLocale(null)); + assertNull(LocaleUtils.toLocale((String) null)); assertValidToLocale("us"); assertValidToLocale("fr"); @@ -524,11 +543,11 @@ public void testLang865() { @ParameterizedTest @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 - final Locale locale = new Locale(l.getLanguage(), l.getCountry(), l.getVariant()); - if (l.equals(locale)) { // it is possible for LocaleUtils.toLocale to handle these Locales - final String str = l.toString(); + final Locale locale = new Locale(actualLocale.getLanguage(), actualLocale.getCountry(), actualLocale.getVariant()); + if (actualLocale.equals(locale)) { // it is possible for LocaleUtils.toLocale to handle these Locales + final String str = actualLocale.toString(); // Look for the script/extension suffix int suff = str.indexOf("_#"); if (suff == - 1) { @@ -541,7 +560,7 @@ public void testParseAllLocales(final Locale l) { localeStr = str.substring(0, suff); } final Locale loc = LocaleUtils.toLocale(localeStr); - assertEquals(l, loc); + assertEquals(actualLocale, loc); } } }