diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index cc793b616..1584ee8c9 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -57,7 +57,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@4fa2a7953630fd2f3fb380f21be14ede0169dd4f # 3.25.12 + uses: github/codeql-action/init@2d790406f505036ef40ecba973cc774a50395aac # 3.25.13 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -68,7 +68,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@4fa2a7953630fd2f3fb380f21be14ede0169dd4f # 3.25.12 + uses: github/codeql-action/autobuild@2d790406f505036ef40ecba973cc774a50395aac # 3.25.13 # ℹī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -82,4 +82,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@4fa2a7953630fd2f3fb380f21be14ede0169dd4f # 3.25.12 + uses: github/codeql-action/analyze@2d790406f505036ef40ecba973cc774a50395aac # 3.25.13 diff --git a/.github/workflows/scorecards-analysis.yml b/.github/workflows/scorecards-analysis.yml index 3dc1433d6..bec68e97c 100644 --- a/.github/workflows/scorecards-analysis.yml +++ b/.github/workflows/scorecards-analysis.yml @@ -64,6 +64,6 @@ jobs: retention-days: 5 - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@4fa2a7953630fd2f3fb380f21be14ede0169dd4f # 3.25.12 + uses: github/codeql-action/upload-sarif@2d790406f505036ef40ecba973cc774a50395aac # 3.25.13 with: sarif_file: results.sarif diff --git a/src/changes/changes.xml b/src/changes/changes.xml index e1d190482..9438361e8 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -46,14 +46,16 @@ The type attribute can be add,update,fix,remove. - - Reimplement StopWatch internals to use java.time. - - Add StopWatch.getSplitDuration() and deprecate getSplitTime(). - Add StopWatch.getStartInstant() and deprecate getStartTime(). - Add StopWatch.getStopInstant() and deprecate getStopTime(). - Add StopWatch.getDuration() and deprecate getTime(). - + + Reimplement StopWatch internals to use java.time. + RandomStringUtils.random() with a negative character index should throw IllegalArgumentException. + LocaleUtils.toLocale(String) cannot parse four segments. + + Add StopWatch.getSplitDuration() and deprecate getSplitTime(). + Add StopWatch.getStartInstant() and deprecate getStartTime(). + Add StopWatch.getStopInstant() and deprecate getStopTime(). + Add StopWatch.getDuration() and deprecate getTime(). + diff --git a/src/main/java/org/apache/commons/lang3/LocaleUtils.java b/src/main/java/org/apache/commons/lang3/LocaleUtils.java index bd6b97447..39b81a490 100644 --- a/src/main/java/org/apache/commons/lang3/LocaleUtils.java +++ b/src/main/java/org/apache/commons/lang3/LocaleUtils.java @@ -52,18 +52,31 @@ public class LocaleUtils { AVAILABLE_LOCALE_SET = Collections.unmodifiableSet(new HashSet<>(list)); } } + + /** + * The underscore character {@code '}{@value}{@code '}. + */ private static final char UNDERSCORE = '_'; + + /** + * The undetermined language {@value}. + */ private static final String UNDETERMINED = "und"; + /** + * The dash character {@code '}{@value}{@code '}. + */ private static final char DASH = '-'; - /** Concurrent map of language locales by country. */ - private static final ConcurrentMap> cLanguagesByCountry = - new ConcurrentHashMap<>(); + /** + * Concurrent map of language locales by country. + */ + private static final ConcurrentMap> cLanguagesByCountry = new ConcurrentHashMap<>(); - /** Concurrent map of country locales by language. */ - private static final ConcurrentMap> cCountriesByLanguage = - new ConcurrentHashMap<>(); + /** + * Concurrent map of country locales by language. + */ + private static final ConcurrentMap> cCountriesByLanguage = new ConcurrentHashMap<>(); /** * Obtains an unmodifiable list of installed locales. @@ -236,28 +249,30 @@ public class LocaleUtils { } /** - * Tries to parse a locale from the given String. + * Tries to parse a Locale from the given String. + *

+ * See {@Link Locale} for the format. + *

* - * @param str the String to parse a locale from. - * @return a Locale instance parsed from the given String. + * @param str the String to parse as a Locale. + * @return a Locale parsed from the given String. * @throws IllegalArgumentException if the given String can not be parsed. + * @see Locale */ private static Locale parseLocale(final String str) { if (isISO639LanguageCode(str)) { return new Locale(str); } - - final String[] segments = str.indexOf(UNDERSCORE) != -1 - ? str.split(String.valueOf(UNDERSCORE), -1) - : str.split(String.valueOf(DASH), -1); + final int limit = 3; + final char separator = str.indexOf(UNDERSCORE) != -1 ? UNDERSCORE : DASH; + final String[] segments = str.split(String.valueOf(separator), 3); final String language = segments[0]; if (segments.length == 2) { final String country = segments[1]; - if (isISO639LanguageCode(language) && isISO3166CountryCode(country) || - isNumericAreaCode(country)) { + if (isISO639LanguageCode(language) && isISO3166CountryCode(country) || isNumericAreaCode(country)) { return new Locale(language, country); } - } else if (segments.length == 3) { + } else if (segments.length == limit) { final String country = segments[1]; final String variant = segments[2]; if (isISO639LanguageCode(language) && diff --git a/src/main/java/org/apache/commons/lang3/RandomStringUtils.java b/src/main/java/org/apache/commons/lang3/RandomStringUtils.java index e30e214eb..eb6615e75 100644 --- a/src/main/java/org/apache/commons/lang3/RandomStringUtils.java +++ b/src/main/java/org/apache/commons/lang3/RandomStringUtils.java @@ -232,6 +232,8 @@ public class RandomStringUtils { } } else if (end <= start) { throw new IllegalArgumentException("Parameter end (" + end + ") must be greater than start (" + start + ")"); + } else if (start < 0 || end < 0) { + throw new IllegalArgumentException("Character positions MUST be >= 0"); } if (end > Character.MAX_CODE_POINT) { diff --git a/src/test/java/org/apache/commons/lang3/LocaleUtilsTest.java b/src/test/java/org/apache/commons/lang3/LocaleUtilsTest.java index c1f7500c3..dc9e3969d 100644 --- a/src/test/java/org/apache/commons/lang3/LocaleUtilsTest.java +++ b/src/test/java/org/apache/commons/lang3/LocaleUtilsTest.java @@ -538,11 +538,10 @@ public class LocaleUtilsTest extends AbstractLangTest { assertValidToLocale("us_EN_a", "us", "EN", "A"); assertValidToLocale("us_EN_SFsafdFDsdfF", "us", "EN", "SFSAFDFDSDFF"); } - - assertThrows( - IllegalArgumentException.class, () -> LocaleUtils.toLocale("us_EN-a"), "Should fail as no consistent delimiter"); - assertThrows( - IllegalArgumentException.class, () -> LocaleUtils.toLocale("uu_UU_"), "Must be 3, 5 or 7+ in length"); + assertThrows(IllegalArgumentException.class, () -> LocaleUtils.toLocale("us_EN-a"), "Should fail as no consistent delimiter"); + assertThrows(IllegalArgumentException.class, () -> LocaleUtils.toLocale("uu_UU_"), "Must be 3, 5 or 7+ in length"); + // LANG-1741 + assertEquals(new Locale("en", "001", "US_POSIX"), LocaleUtils.toLocale("en_001_US_POSIX")); } /** diff --git a/src/test/java/org/apache/commons/lang3/RandomStringUtilsTest.java b/src/test/java/org/apache/commons/lang3/RandomStringUtilsTest.java index 876a0d146..2999d8ebc 100644 --- a/src/test/java/org/apache/commons/lang3/RandomStringUtilsTest.java +++ b/src/test/java/org/apache/commons/lang3/RandomStringUtilsTest.java @@ -93,23 +93,43 @@ public class RandomStringUtilsTest extends AbstractLangTest { } @Test - public void testExceptions() { - final char[] DUMMY = { 'a' }; // valid char array + public void testExceptionsRandom() { assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(-1)); assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(-1, true, true)); - assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(-1, DUMMY)); + assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(-1, new char[] { 'a' })); assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(1, new char[0])); assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(-1, "")); assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(-1, (String) null)); assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(-1, 'a', 'z', false, false)); - assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(-1, 'a', 'z', false, false, DUMMY)); - assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(-1, 'a', 'z', false, false, DUMMY, new Random())); + assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(-1, 'a', 'z', false, false, new char[] { 'a' })); + assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(-1, 'a', 'z', false, false, new char[] { 'a' }, new Random())); assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(8, 32, 48, false, true)); assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(8, 32, 65, true, false)); + assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.random(1, Integer.MIN_VALUE, -10, false, false, null)); + } + + @Test + public void testExceptionsRandomAlphabetic() { assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.randomAlphabetic(-1)); + } + + @Test + public void testExceptionsRandomAscii() { assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.randomAscii(-1)); + } + + @Test + public void testExceptionsRandomGraph() { assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.randomGraph(-1)); + } + + @Test + public void testExceptionsRandomNumeric() { assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.randomNumeric(-1)); + } + + @Test + public void testExceptionsRandomPrint() { assertThrows(IllegalArgumentException.class, () -> RandomStringUtils.randomPrint(-1)); } diff --git a/src/test/java/org/apache/commons/lang3/time/FastDateFormatTest.java b/src/test/java/org/apache/commons/lang3/time/FastDateFormatTest.java index aa13e2845..698dd1ade 100644 --- a/src/test/java/org/apache/commons/lang3/time/FastDateFormatTest.java +++ b/src/test/java/org/apache/commons/lang3/time/FastDateFormatTest.java @@ -47,6 +47,9 @@ import org.junitpioneer.jupiter.DefaultTimeZone; * Unit tests {@link org.apache.commons.lang3.time.FastDateFormat}. */ public class FastDateFormatTest extends AbstractLangTest { + + private static final String ISO_8601_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZZ"; + private static final int NTHREADS = 10; private static final int NROUNDS = 10000; @@ -245,7 +248,7 @@ public class FastDateFormatTest extends AbstractLangTest { } @Test - public void testLANG_1152() { + public void testLang1152() { final TimeZone utc = FastTimeZone.getGmtTimeZone(); final Date date = new Date(Long.MAX_VALUE); @@ -256,15 +259,29 @@ public class FastDateFormatTest extends AbstractLangTest { assertEquals("17/08/292278994", dateAsString); } @Test - public void testLANG_1267() { + public void testLang1267() { FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"); } + @Test + public void testLang1641() { + assertSame(FastDateFormat.getInstance(ISO_8601_DATE_FORMAT), FastDateFormat.getInstance(ISO_8601_DATE_FORMAT)); + // commons-lang's GMT TimeZone + assertSame(FastDateFormat.getInstance(ISO_8601_DATE_FORMAT, FastTimeZone.getGmtTimeZone()), + FastDateFormat.getInstance(ISO_8601_DATE_FORMAT, FastTimeZone.getGmtTimeZone())); + // default TimeZone + assertSame(FastDateFormat.getInstance(ISO_8601_DATE_FORMAT, TimeZone.getDefault()), + FastDateFormat.getInstance(ISO_8601_DATE_FORMAT, TimeZone.getDefault())); + // TimeZones that are identical in every way except ID + assertNotSame(FastDateFormat.getInstance(ISO_8601_DATE_FORMAT, TimeZone.getTimeZone("Australia/Broken_Hill")), + FastDateFormat.getInstance(ISO_8601_DATE_FORMAT, TimeZone.getTimeZone("Australia/Yancowinna"))); + } + /** * According to LANG-954 (https://issues.apache.org/jira/browse/LANG-954) this is broken in Android 2.1. */ @Test - public void testLANG_954() { + public void testLang954() { final String pattern = "yyyy-MM-dd'T'"; FastDateFormat.getInstance(pattern); }