This is an automated email from the ASF dual-hosted git repository. ggregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-lang.git
commit c9e825e823e30c5b1e3ddc9de5e8fd0094d52ee5 Author: Gary Gregory <garydgreg...@gmail.com> AuthorDate: Mon Dec 21 12:56:19 2020 -0500 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 + .../java/org/apache/commons/lang3/LocaleUtils.java | 12 +++++++- .../java/org/apache/commons/lang3/StringUtils.java | 4 +-- .../commons/lang3/text/ExtendedMessageFormat.java | 9 +++--- .../org/apache/commons/lang3/time/DateUtils.java | 3 +- .../apache/commons/lang3/time/FastDateParser.java | 16 ++++++----- .../apache/commons/lang3/time/FastDatePrinter.java | 7 +++-- .../org/apache/commons/lang3/time/FormatCache.java | 21 ++++++-------- .../org/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 25a2839..5804a9c 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -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 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 and use LocaleUtils.toLocale(Locale) to avoid NPEs.</action> <!-- UPDATES --> <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> diff --git a/src/main/java/org/apache/commons/lang3/LocaleUtils.java b/src/main/java/org/apache/commons/lang3/LocaleUtils.java index 24b7f3e..b73856e 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 @@ public class LocaleUtils { 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> * diff --git a/src/main/java/org/apache/commons/lang3/StringUtils.java b/src/main/java/org/apache/commons/lang3/StringUtils.java index ab9d952..a35b002 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 class StringUtils { 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 class StringUtils { 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 6b921b8..c0cd9b3 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.Locale; 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 class ExtendedMessageFormat extends MessageFormat { /** * Create a new ExtendedMessageFormat. * - * @param pattern the pattern to use, not null - * @param locale the locale to use, not null - * @param registry the registry of format factories, may be null + * @param pattern the pattern to use, not null. + * @param locale the locale to use. + * @param registry the registry of format factories, may be null. * @throws IllegalArgumentException in case of a bad pattern. */ public ExtendedMessageFormat(final String pattern, final Locale locale, final Map<String, ? extends FormatFactory> 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 fddfcbb..4b20c54 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.NoSuchElementException; 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 @@ public class DateUtils { } 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 e53a246..9ae683b 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.concurrent.ConcurrentMap; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.apache.commons.lang3.LocaleUtils; + /** * <p>FastDateParser is a fast and thread-safe version of * {@link java.text.SimpleDateFormat}.</p> @@ -125,15 +127,15 @@ public class FastDateParser implements DateParser, Serializable { 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 @@ public class FastDateParser implements DateParser, Serializable { * @param regex The regular expression to build * @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<>(); - + locale = LocaleUtils.toLocale(locale); final Map<String, Integer> displayNames = cal.getDisplayNames(field, Calendar.ALL_STYLES, locale); final TreeSet<String> sorted = new TreeSet<>(LONGER_FIRST_LOWERCASE); for (final Map.Entry<String, Integer> displayName : displayNames.entrySet()) { @@ -697,7 +699,7 @@ public class FastDateParser implements DateParser, Serializable { */ 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 @@ public class FastDateParser implements DateParser, Serializable { * @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 3402409..bc5cda5 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.TimeZone; 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 @@ public class FastDatePrinter implements DatePrinter, Serializable { * @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 @@ public class FastDatePrinter implements DatePrinter, Serializable { } 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 e1e3a01..4786cd9 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.TimeZone; 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 @@ abstract class FormatCache<F extends Format> { 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 @@ abstract class FormatCache<F extends Format> { */ // 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 @@ abstract class FormatCache<F extends Format> { */ // 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 @@ abstract class FormatCache<F extends Format> { 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; } - // ---------------------------------------------------------------------- /** * <p>Helper class to hold multi-part Map keys</p> */ diff --git a/src/test/java/org/apache/commons/lang3/LocaleUtilsTest.java b/src/test/java/org/apache/commons/lang3/LocaleUtilsTest.java index 558b5f9..8a8513d 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 @@ public class LocaleUtilsTest { } /** - * 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 class LocaleUtilsTest { @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 class LocaleUtilsTest { localeStr = str.substring(0, suff); } final Locale loc = LocaleUtils.toLocale(localeStr); - assertEquals(l, loc); + assertEquals(actualLocale, loc); } } }